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 Agora Laravel Package

tomatophp/laravel-agora

Integrate Agora real-time voice and video into your Laravel app with ready-made helpers for channel and token generation, configuration management, and quick setup so you can add live audio and video features without writing boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

This package does one thing: mint Agora RTC tokens server-side. Joining a channel, rendering video, muting tracks — that is entirely the Agora client SDK's job. Internalize that boundary before you install anything, because it determines what you will and won't find in src/.

Install and publish:

composer require tomatophp/laravel-agora
php artisan vendor:publish --tag=agora-config   # verify the actual tag in README / src

Then set credentials from the Agora console (Project Management → Configure):

AGORA_APP_ID=your_app_id
AGORA_APP_CERTIFICATE=your_app_certificate

The App Certificate is a signing secret. It stays on the server — never in a client bundle, never in a log line, never in a committed .env.example.

Where to look first, in order:

  1. config/agora.php after publishing — exact key names for App ID, certificate, and any TTL/expiry defaults, plus whether values are read via env() or config().
  2. The service provider — tells you the container binding name and whether the service is a singleton.
  3. src/ end to end. The whole package is a few hundred lines. You are adopting source code, not consuming a documented API; the source is the spec. Note the exact method signature for token generation before writing any integration code.

Your first use case is this loop:

  1. Authenticated user hits POST /rooms/{room}/token on your Laravel app.
  2. You authorize the request, then call the package service to mint a token.
  3. The client receives { appId, channel, uid, token, expiresIn } and passes it to AgoraRTC.joinChannel(token, channel, uid).
  4. When the client's onTokenPrivilegeWillExpire fires, it calls the same endpoint for a fresh token.

Get steps 1–3 working against a real join on one platform (Web is the fastest loop) before anything else. A token that "generates successfully" proves nothing — only a successful join does.

Implementation Patterns

Put the package behind an application-owned contract

Highest-leverage ten minutes you'll spend. Keep the vendor name out of controllers and domain code.

// app/Contracts/AgoraTokenIssuer.php
interface AgoraTokenIssuer
{
    public function issue(string $channel, string $uid, string $role, int $ttlSeconds): string;
}
// app/Services/Agora/AgoraTokenIssuerAdapter.php
final class AgoraTokenIssuerAdapter implements AgoraTokenIssuer
{
    public function issue(string $channel, string $uid, string $role, int $ttlSeconds): string
    {
        // Match the package's real signature — read src/ first.
        return Agora::generateToken($channel, $uid, $role, $ttlSeconds);
    }
}

Bind it in a service provider. Now the package appears in exactly one file. When you fork the class, upgrade, or swap to Agora's official PHP library, the diff is one class and your tests don't move.

Derive channel, uid, role, and TTL server-side

The API should accept intent ("join room 42 of team 7"), never a raw channel name or role string. Client-supplied channel names are channel squatting waiting to happen.

final class IssueRoomToken
{
    public function __construct(private AgoraTokenIssuer $issuer) {}

    public function __invoke(User $user, Team $team, string $roomId): array
    {
        Gate::authorize('joinRoom', [$team, $roomId]);

        $channel = "t{$team->id}-room-{$roomId}";                    // namespaced
        $uid     = (string) $user->id;                               // derived, not accepted
        $role    = $user->can('publish', $team) ? 'publisher' : 'subscriber';
        $ttl     = $team->plan->agoraTokenTtlSeconds();

        return [
            'appId'     => config('agora.app_id'),
            'channel'   => $channel,
            'uid'       => $uid,
            'token'     => $this->issuer->issue($channel, $uid, $role, $ttl),
            'expiresIn' => $ttl,
        ];
    }
}

The uid decision is easy to get wrong and painful to debug: Agora treats integer and string UIDs as distinct namespaces. Pick one — stringified internal user IDs or an opaque per-session ID — and be consistent everywhere, or you get ghost duplicate participants.

Treat TTL and role as product configuration

Token TTL is a monetizable lever: 40-minute free sessions, all-day paid webinars, audience-only versus publisher seats. Keep it in a plan/tier model, not a hardcoded constant at the call site, so product can experiment without a deploy.

Prefer a short TTL plus client-driven renewal over a long-lived token. A 30–60 minute token refreshed via onTokenPrivilegeWillExpire is safer and more flexible than a 24-hour token you can't revoke.

Make the endpoint boring

Route::middleware(['auth:sanctum', 'throttle:agora-tokens'])
    ->post('/teams/{team}/rooms/{room}/token', IssueRoomToken::class);
  • Authenticated, rate-limited per user (renewal loops exist; a buggy client will hammer this).
  • Audit-log the issuance event (user, channel, role, TTL, request ID) — not the token.
  • The mint is a stateless local HMAC computation, so it scales for free. Auth and rate limits are the real ceiling.

Testing

The package ships no test that validates a minted token against Agora's own validator. Write two:

  1. Determinism test with Carbon::setTestNow() — same inputs and TTL must produce the same token. Catches expiry-unit mistakes cold.
  2. Privilege/shape test — decode the token and assert role privileges and expiration match policy. This is what catches a wrong default role silently granting publish rights to audience members.

Filament / Blade fast path

For admin panels and internal tools, the Blade/Filament-friendly setup gets you a join screen in an afternoon. Keep the token endpoint a normal authenticated route and pass appId, channel, uid, token into the view — don't let the view call the service directly.

Gotchas and Tips

A missing or rotated App Certificate is a 100% join failure across all users, and it presents as a client bug. Everyone just "can't connect." Keep a synthetic join check running on a schedule in staging, and write the rotation runbook before you need it.

Verify the release history yourself. Check Packagist and GitHub tags rather than trusting metadata. You're effectively on a single-maintainer package with zero dependents. Pin the commit in composer.json and name an internal owner — or fork the ~150 lines of real logic into app/Support/Agora under MIT. For a security-critical signing path, that's a defensible default.

config:cache breaks raw env() calls. If the service reads env('AGORA_APP_ID') at runtime instead of through config(), tokens work locally and fail in production. If you spot it in the source, fix it in your fork: surface the keys in config/agora.php and read via config(). Run php artisan config:cache in CI.

Token version drift. AccessToken v1 vs v2 is not cosmetic — newer Agora project configurations reject v1 outright. Check which builder the package wraps. If the builder is vendored rather than a Composer dependency on Agora's official library, you've inherited a snapshot and own the schema-watch.

Clock skew yields "token not yet valid" / "token expired" at join. Your server clock is the source of truth. Run NTP; don't backdate tokens to "give them more time."

Never accept a client channel name verbatim. Namespace by tenant/room and enforce membership server-side, or you get cross-tenant token reuse and channel squatting with no after-the-fact detection.

Don't log tokens — not request logs, not audit trails, not exception payloads. Log the issuance event and correlate by request ID.

Renewal is where production bugs live. onTokenPrivilegeWillExpire is the path nobody tests until a long session drops. Exercise it in staging with a deliberately short TTL (e.g. 120s) so you hit renewal inside one manual session.

Namespace collisions. agora is a generic config key and facade alias. If you later add Agora Chat, RTM, Cloud Recording, or webhooks, check aliases and config namespaces first — this package covers tokens only. Recording, Media Push, Chat/RTM, REST wrappers, and webhook handling are all absent and must be built separately.

When you outgrow the wrapper (RTM/Chat tokens, per-tenant App IDs for resale, wildcard channels, fine-grained privilege expiry), check whether the underlying builder is exposed. If not, implement your AgoraTokenIssuer against Agora's official PHP library — the interface you wrote on day one makes that swap a single file.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings