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

Php Slack Laravel Package

frlnc/php-slack

A lightweight PHP client for the Slack API, giving PHP and Laravel applications a simple way to call Slack Web API methods and integrate notifications, channels, and workspace data without heavy dependencies.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and keep the token out of code:

composer require frlnc/php-slack
// config/services.php
'slack' => [
    'token' => env('SLACK_TOKEN'), // xoxb-... bot token
],

Register the client once in a service provider. Commander is stateless per token, so a singleton is appropriate for single-workspace apps:

// app/Providers/AppServiceProvider.php
use Frlnc\Slack\Core\Commander;
use Frlnc\Slack\Http\CurlInteractor;

public function register(): void
{
    $this->app->singleton(Commander::class, function () {
        $interactor = new CurlInteractor();
        $interactor->setTimeout(10);

        return new Commander(config('services.slack.token'), $interactor);
    });
}

Your first useful call is almost always posting a message:

$response = app(Commander::class)->post('chat.postMessage', [
    'channel' => 'C0123456789',
    'text'    => 'Deploy finished on ' . config('app.env'),
]);

$body = $response->getBody(); // decoded array

if (! ($body['ok'] ?? false)) {
    throw new RuntimeException('Slack: ' . ($body['error'] ?? 'unknown'));
}

Key files to read before doing anything non-trivial:

  • src/Frlnc/Slack/Core/Commander.php — how endpoint names, the token, and HTTP verbs are assembled.
  • src/Frlnc/Slack/Http/CurlInteractor.php — transport, timeouts, encoding of request parameters, SSL options.
  • src/Frlnc/Slack/Http/SlackResponse.php — getBody(), getStatusCode(), getRawBody(), headers.
  • src/Frlnc/Slack/Contracts/Http/Interactor.php — the seam you will likely implement for retries/logging.
  • README.md — endpoint naming convention (chat.postMessage, not a URL).

The package is a thin HTTP wrapper over https://slack.com/api/{method}. It does not validate responses, paginate, retry, or format messages. Plan to build those yourself.

Implementation Patterns

Wrap the Commander in your own service. Do not scatter $commander->post(...) through controllers. Centralize the ok check and typed helpers:

namespace App\Services;

use Frlnc\Slack\Core\Commander;
use RuntimeException;

class SlackClient
{
    public function __construct(private Commander $commander) {}

    public function postMessage(string $channel, string $text, array $extra = []): array
    {
        $response = $this->commander->post('chat.postMessage', array_merge([
            'channel' => $channel,
            'text'    => $text,
        ], $extra));

        return $this->assertOk($response->getBody(), 'chat.postMessage');
    }

    public function call(string $method, array $params = [], string $verb = 'post'): array
    {
        $response = $this->commander->{$verb}($method, $params);

        return $this->assertOk($response->getBody(), $method);
    }

    private function assertOk(?array $body, string $method): array
    {
        if (! is_array($body) || ! ($body['ok'] ?? false)) {
            throw new RuntimeException(
                "Slack {$method} failed: " . ($body['error'] ?? 'invalid response')
            );
        }

        return $body;
    }
}

Use a Laravel notification channel. Store slack_id on users, implement routeNotificationForSlack(), and let the channel call your SlackClient. This is the cleanest integration for user-directed messages and keeps Slack payload formatting near the notification class.

public function routeNotificationForSlack(): ?string
{
    return $this->slack_id; // C… or U… ID, not a display name
}

Queue writes and handle retryable errors explicitly. Slack write endpoints are network-bound and rate limited; jobs give you backoff. Only retry transient failures:

public function handle(SlackClient $slack): void
{
    try {
        $slack->postMessage($this->channel, $this->text);
    } catch (RuntimeException $e) {
        $retryable = str_contains($e->getMessage(), [
            'ratelimited', 'internal_error', 'service_unavailable', 'fatal_error',
        ]);

        if ($retryable) {
            $this->release(30); // or throw for Laravel's backoff
            return;
        }

        throw $e; // channel_not_found, invalid_auth, missing_scope are permanent
    }
}

Paginate manually with cursor. There is no pagination helper:

$cursor = '';

do {
    $body = $slack->call('users.list', array_filter([
        'limit'  => 200,
        'cursor' => $cursor !== '' ? $cursor : null,
    ]), 'get');

    foreach ($body['members'] as $member) {
        // ...
    }

    $cursor = $body['response_metadata']['next_cursor'] ?? '';
} while ($cursor !== '');

Extend the interactor for logging, headers, or retries. This is the package's main extension point. Prefer composition (implement Interactor with Guzzle) when you need real retry logic or response headers:

class LoggingInteractor extends CurlInteractor
{
    public function execute($method, $url, array $parameters = [], array $headers = [])
    {
        $start = microtime(true);
        $response = parent::execute($method, $url, $parameters, $headers);

        Log::info('slack.request', [
            'method'     => $method,
            'url'        => $url,
            'status'     => $response->getStatusCode(),
            'duration_ms'=> (int) ((microtime(true) - $start) * 1000),
            // never log $parameters: it contains the token
        ]);

        return $response;
    }
}

Fake the interactor in tests. Http::fake() will not intercept this package because it uses cURL directly. Bind a fake Interactor that returns canned SlackResponse objects, or mock Commander with Mockery. Assert on the recorded parameters to prove the payload shape.

Build a factory for multi-workspace apps. A singleton Commander carries one token. For multiple teams, resolve a Commander per token via a factory keyed by workspace/team ID; do not share one instance.

Format rich messages carefully. Because parameters are form-encoded, blocks, attachments, and similar structured fields must be JSON strings:

$slack->postMessage($channelId, 'Deployment complete', [
    'blocks' => json_encode([/* Block Kit array */]),
]);

Store returned ts values. chat.postMessage returns ts and channel. Persist them if you plan to thread replies (thread_ts) or edit messages later (chat.update requires both channel and ts).

Gotchas and Tips

  • HTTP 200 does not mean success. Slack returns {"ok": false, "error": "..."} with a 200 status. The package never throws. Every call path must check ok, including reads.
  • The package is unmaintained since 2016. Treat its transport as legacy. Read CurlInteractor in the installed version before production; check timeouts, SSL verification, and parameter encoding. If SSL peer verification is disabled there, subclass and enable it.
  • Form encoding mangles nested arrays. CurlInteractor uses query-string style encoding, so ['blocks' => [...]] becomes blocks[0][type]=... rather than the JSON Slack expects. Always json_encode() structured payloads.
  • The token is appended as a request parameter. For get() calls it can appear in the URL/query string and leak into proxy or access logs. Prefer post() for everything, or use a custom interactor that sends Authorization: Bearer ….
  • No rate-limit handling. Slack returns 429 with Retry-After, or {"ok":false,"error":"ratelimited"} on older paths. Detect both, release the job with that delay, and add jitter. The package does not inspect headers or back off for you.
  • No pagination. channels.list, users.list, conversations.history, and friends truncate. Loop response_metadata.next_cursor; an empty string means done. Expect legacy paging objects on some endpoints too.
  • files.upload is awkward. Form-encoding plus local files requires CURLFile, which this interactor generally does not handle cleanly. For text files, pass content directly (under Slack's 1 MB limit); for larger/binary files, use a custom Guzzle-backed interactor or a separate upload client.
  • Endpoint names are bare method names. Use 'chat.postMessage', not a full URL or a leading slash. Commander prepends the API base URL, so incoming webhooks and arbitrary URLs are not supported.
  • Use channel and user IDs, not names. #general or @alice sometimes work but are brittle across renames and private channels. Resolve once with conversations.list/users.list and cache the IDs.
  • Common Slack errors map to configuration problems. invalid_auth/not_authed means a bad or revoked token; missing_scope includes needed and provided fields — log them and fix app scopes; not_in_channel means invite the bot or grant chat:write.public; channel_not_found usually means you passed a name instead of an ID.
  • Bot tokens ignore username and icon_emoji. as_user and identity overrides are legacy behavior. Set the bot's display identity in the Slack app configuration instead.
  • Retries can double-post. Slack has no idempotency key for chat.postMessage. Only retry on 429/5xx/network failures, and where duplicates matter, record a dedupe key or the returned ts before retrying.
  • API response shapes have drifted since 2016. Prefer conversations.* over channels.* for new work, expect newer fields on files.* and users.*, and test payloads against the live API rather than assuming the README's examples still reflect Slack's current responses.
  • Missing capabilities. There is no Events API listener, RTM/Socket Mode, OAuth flow helper, or webhook sender. This is a synchronous Web API caller only; pair it with a separate package or custom code for event ingestion and app installation.
  • Redact the token when debugging. If you add logging to the interactor, log the method and status only — never the full parameter array, which always contains token.
  • Lock down Composer constraints if needed. The package's PHP requirement is old but the code itself is loose. If dependency resolution fights your Laravel/PHP version, pin or fork it and run composer why-not frlnc/php-slack to see the conflict.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager