laravel-challonge maintained by 3gdesigner
Challonge API v2.1 Wrapper for Laravel
A modern, fluent Laravel package to interact with the Challonge API v2.1 (as documented on Challonge API Reference).
Unlike legacy v1 packages, this wrapper supports:
- Challonge API v2.1 endpoints with native
JSON:APIrequest bodies (application/vnd.api+json). - Dual authentication methods:
- Developer API Key (
Authorization-Type: v1) - OAuth Bearer Token (
Authorization-Type: v2)
- Developer API Key (
- Fluent resources for Tournaments, Participants, Matches, Match Attachments, and Stations.
- Laravel Facade, service container binding, and configuration publishing.
Requirements
- PHP 8.4+
- Laravel 12.x or 13.x
Installation
Install the package via Composer:
composer require 3gdesigner/laravel-challonge
Publish the configuration file:
php artisan vendor:publish --tag="challonge-config"
Configure your .env file:
CHALLONGE_KEY=your_api_key_or_oauth_token
CHALLONGE_AUTH_TYPE=v1 # 'v1' for developer API key, 'v2' for OAuth bearer token
CHALLONGE_BASE_URL=https://api.challonge.com/v2.1
Quick Usage
Facade
use ThreeGDesigner\Challonge\Facades\Challonge;
// Check current user profile
$me = Challonge::me();
$username = $me->attribute('username');
// Tournaments
$tournaments = Challonge::tournaments()->all(['page' => 1, 'per_page' => 25]);
foreach ($tournaments->collection() as $t) {
echo $t->id() . ': ' . $t->attribute('name');
}
$tournament = Challonge::tournaments()->find('my_tournament_slug');
// Create tournament
$newTournament = Challonge::tournaments()->create([
'name' => 'Championship 2026',
'tournament_type' => 'single elimination',
'url' => 'champ_2026_' . uniqid(),
]);
// Change tournament state
Challonge::tournaments()->start('my_tournament_slug');
Challonge::tournaments()->finalize('my_tournament_slug');
Challonge::tournaments()->reset('my_tournament_slug');
// Two-Stage Tournaments (v2.1)
Challonge::tournaments()->startGroupStage('my_tournament_slug');
Challonge::tournaments()->finalizeGroupStage('my_tournament_slug');
// Any other documented state (process_checkin, abort_checkin, open_predictions, ...)
use ThreeGDesigner\Challonge\Enums\TournamentStateAction;
Challonge::tournaments()->changeState('my_tournament_slug', TournamentStateAction::ProcessCheckIn);
// Participants
$participants = Challonge::participants('my_tournament_slug')->all();
$participant = Challonge::participants('my_tournament_slug')->create([
'name' => 'Player 1',
'seed' => 1,
]);
Challonge::participants('my_tournament_slug')->bulkAdd([ // 1 to 20 participants
['name' => 'Player 2', 'seed' => 2],
['name' => 'Player 3', 'seed' => 3],
]);
Challonge::participants('my_tournament_slug')->randomize();
Challonge::participants('my_tournament_slug')->registerMe(['name' => 'My Team']); // OAuth application scope
// Matches
$matches = Challonge::matches('my_tournament_slug')->all();
// Report scores and the winner: participant ID => that participant's game scores
// (a 3-1, 3-0 win for player 76 over player 77)
Challonge::matches('my_tournament_slug')->reportScore(
matchId: $matchId,
scores: ['76' => [3, 3], '77' => [1, 0]],
winnerId: '76',
);
// Report a tie (Swiss / Round Robin)
Challonge::matches('my_tournament_slug')->reportScore(
matchId: $matchId,
scores: ['76' => '1', '77' => '1'],
tie: true,
);
Challonge::matches('my_tournament_slug')->markUnderway($matchId);
Challonge::matches('my_tournament_slug')->reopen($matchId);
// Stations & Queues
$stations = Challonge::stations('my_tournament_slug')->all();
Challonge::stations('my_tournament_slug')->queue(stationId: '1', matchId: $matchId); // end of queue, or position: 1
$queue = Challonge::stations('my_tournament_slug')->queuers(stationId: '1');
Challonge::stations('my_tournament_slug')->dequeue(stationId: '1', stationQueuerId: $queue->collection()->first()->id());
Fluent Response (ChallongeResponse)
All resource methods return a ChallongeResponse implementing ArrayAccess, Countable, and JsonSerializable:
$res = Challonge::tournaments()->find('100');
// Fluent helpers
$id = $res->id();
$type = $res->type();
$name = $res->attribute('name');
$attrs = $res->attributes();
// Standard ArrayAccess (backwards compatible)
$name = $res['data']['attributes']['name'];
// Collections for list endpoints
$list = Challonge::tournaments()->all();
$collection = $list->collection(); // Illuminate\Support\Collection<ChallongeResponse>
Error Handling
When the Challonge API returns HTTP 4xx or 5xx, a ChallongeApiException is thrown with parsed JSON:API error details:
use ThreeGDesigner\Challonge\Exceptions\ChallongeApiException;
try {
Challonge::tournaments()->create(['name' => '']);
} catch (ChallongeApiException $e) {
echo $e->getStatusCode(); // e.g. 422
echo $e->getFirstErrorMessage(); // e.g. "Name can't be blank"
print_r($e->getErrors()); // full JSON:API errors array
}
Runtime Credentials Override
use ThreeGDesigner\Challonge\Facades\Challonge;
// Temporarily use another account's API key
$tournaments = Challonge::withApiKey('another_api_key')->tournaments()->all();
// Use an OAuth token (sent as `Authorization-Type: v2` + `Authorization: Bearer <token>`)
$tournaments = Challonge::withToken('user_oauth_bearer_token')->tournaments()->all();
OAuth 2.0 (act on behalf of Challonge users)
Add your developer application's credentials to .env (the secret must stay server side):
CHALLONGE_CLIENT_ID=...
CHALLONGE_CLIENT_SECRET=...
CHALLONGE_REDIRECT_URI=https://your-app.test/challonge/callback
use ThreeGDesigner\Challonge\Enums\Scope;
use ThreeGDesigner\Challonge\Facades\Challonge;
use ThreeGDesigner\Challonge\OAuth\AccessToken;
use ThreeGDesigner\Challonge\OAuth\ChallongeOAuth;
// 1. Redirect the user to Challonge
Route::get('/challonge/connect', function (ChallongeOAuth $oauth) {
session(['challonge_state' => $state = Str::random(32)]);
return redirect($oauth->authorizationUrl([Scope::Me, Scope::TournamentsRead, Scope::TournamentsWrite], $state));
});
// 2. Exchange the code and store the token (encrypted)
Route::get('/challonge/callback', function (Request $request, ChallongeOAuth $oauth) {
abort_unless(hash_equals(session('challonge_state', ''), (string) $request->query('state')), 403);
$token = $oauth->exchangeCode((string) $request->query('code'));
$request->user()->update(['challonge_token' => encrypt(json_encode($token))]);
});
// 3. Use it, refreshing when expired (tokens last one week)
$token = AccessToken::fromArray(json_decode(decrypt($user->challonge_token), true));
if ($token->isExpired()) {
$token = app(ChallongeOAuth::class)->refresh($token);
// persist the new token...
}
$tournaments = Challonge::withToken($token)->tournaments()->all();
// Server-side application token (client credentials grant, scope application:manage)
$appToken = app(ChallongeOAuth::class)->clientCredentials();
To try the authorization code flow by hand: composer oauth:authorize prints the URL to open; then run
composer oauth:authorize -- <code> with the code Challonge appended to your redirect URI.
Live API test
composer test:live runs scripts/live-api-test.php against the real API using the CHALLONGE_* values from .env.
It creates a private throw-away tournament, exercises every resource (plus the client credentials grant when CHALLONGE_CLIENT_ID/CHALLONGE_CLIENT_SECRET are set) and deletes it at the end
(--keep keeps it, --verbose prints error bodies). It writes real data: never point it at production credentials in CI.
A full run costs about 110 requests and the free Challonge plan allows 500 requests per 30 days (then every call answers HTTP 429), so run it sparingly.
When the account runs out of requests (HTTP 429; the free plan allows 500 requests per 30 days),
a ChallongeRateLimitException (a subclass of ChallongeApiException) is thrown, so you can back off or queue the work:
use ThreeGDesigner\Challonge\Exceptions\ChallongeRateLimitException;
try {
$matches = Challonge::matches($tournamentId)->all();
} catch (ChallongeRateLimitException $e) {
// stop calling Challonge for a while; retrying immediately will keep failing
}
A tournament whose group stage is underway or finalized cannot be reset(); reset the group stage first:
Challonge::tournaments()->changeState($id, TournamentStateAction::ResetGroupStage).
Network failures (no HTTP response) throw ThreeGDesigner\Challonge\Exceptions\ChallongeException with getStatusCode() === null.
ChallongeResponse is read-only: writing through ArrayAccess throws a LogicException.
Testing
Run tests with Pest:
composer test
composer test runs Pint (check mode), PHPStan and Pest. Other scripts:
composer format # fix code style with Pint
composer analyse # Rector (dry-run) + Pint (check) + PHPStan
composer qa # apply Rector and Pint
composer insights # PHP Insights report
License
The MIT License (MIT). Please see License File for more information.