The bridge wires the SteamConnector from a single config file. In most apps the
only value you set is your API key.
Installing
Section titled “Installing”One command covers both setup steps:
php artisan steam:installPublishes config/steam-api.php, then prompts for your
Steam Web API key — hidden as you type — and
appends STEAM_API_KEY to .env.
Safe to re-run: an existing config file is kept, an existing key is replaced
only once you confirm, and a blank answer leaves .env alone.
-
Publish the config file:
Terminal window php artisan vendor:publish --tag=steam-api-config -
Add your Steam Web API key to
.env:.env STEAM_API_KEY=your-steam-web-api-key
The config file
Section titled “The config file”The provider merges its own steam-api config, so the package works unpublished.
Publish it when you want the file in your repo to override the defaults:
php artisan vendor:publish --tag=steam-api-configThis writes config/steam-api.php:
return [
// Your Steam Web API key, obtained from https://steamcommunity.com/dev. // Sent as the "key" query parameter on every request that needs one. 'key' => env('STEAM_API_KEY'),
// Steam's own language code, sent on every request whose payload is // localised. Unset, it follows the application locale. 'language' => env('STEAM_API_LANGUAGE'),
// How long to wait on Steam, in seconds, and how many attempts a request // Steam never answered, or answered with a 5xx, gets in all. 'http' => [ 'connect_timeout' => env('STEAM_API_CONNECT_TIMEOUT'), 'request_timeout' => env('STEAM_API_REQUEST_TIMEOUT'),
'retry' => [ 'tries' => env('STEAM_API_RETRY_TRIES', 1), 'interval' => env('STEAM_API_RETRY_INTERVAL', 0), 'exponential_backoff' => env('STEAM_API_RETRY_EXPONENTIAL_BACKOFF', false), ],
// A Saloon Sender class, built by the container. Unset, Guzzle. 'sender' => env('STEAM_API_SENDER'), ],
// The cache store holding the request counters, the limits replacing // Steam's 100 000 a day, and the seconds a 429 holds requests back. 'rate_limit' => [ 'store' => env('STEAM_API_RATE_LIMIT_STORE'),
'limits' => null,
'cooldown' => env('STEAM_API_RATE_LIMIT_COOLDOWN', 60), ],
// Resolve a route parameter into a SteamId value object. Opt-in. See the // route binding guide for details. 'route_binding' => [ 'enabled' => false, 'parameter' => 'steamId', ],
// Render an unhandled Steam failure as the status it maps to instead of a // blanket 500. On by default. 'exceptions' => [ 'render' => true, ],
// The channel from config/logging.php failed Steam calls are logged to, and // whether every response is too. Unset, nothing is logged. 'logging' => [ 'channel' => env('STEAM_API_LOG_CHANNEL'), 'responses' => env('STEAM_API_LOG_RESPONSES', false), ],
];The API key
Section titled “The API key”Set the key in .env. It is read through config('steam-api.key') and appended to
every request that needs one, so you never pass it per call:
STEAM_API_KEY=your-steam-web-api-keyGet one from the Steam Web API dashboard. The key is
optional: Steam serves the apps() and news() endpoints, currentPlayers(),
globalAchievements(), newsFeed() and webApi()->serverTime() anonymously, so those go out
without one, and webApi()->supportedApis() sends it only when there is one. With
the key unset — or present but blank — any other request raises the base SDK’s
ApiKeyNotConfiguredException. Its message says to pass a key to SteamConfig, which here
means setting STEAM_API_KEY, or steam-api.key directly.
Default language
Section titled “Default language”Steam localises achievement names, descriptions and stat labels, and the connector carries
one language code on every request that reads them. The codes are Valve’s own rather than
ISO ones — koreana, not ko; see Fkrzski\SteamApiSdk\Enums\Language for the list:
STEAM_API_LANGUAGE=polishsteam-api.language |
What the request carries |
|---|---|
| unset — the default | the language the application locale maps to, or nothing where it maps to none |
'' |
nothing; Steam picks |
'polish' |
polish |
| anything else | nothing goes out — InvalidSteamLanguageException instead |
A rejected code raises the first time the connector is resolved, so it surfaces on your
first Steam call rather than at boot. It is a misconfiguration, so it renders as a 500
whatever steam-api.exceptions.render says.
The language: argument on
achievements(),
schema() and
userStats() overrides the default for
that one call.
Following the locale
Section titled “Following the locale”With steam-api.language unset, Localization\LocaleLanguageResolver maps app()->getLocale() through
a table of Valve’s spellings: pl is polish, ko is koreana, pt_BR is brazilian.
A locale whose region Steam does not distinguish falls back to the base one — de_AT is
german — and one Steam publishes no language for sends nothing rather than raising.
Bind Contracts\SteamLanguageResolver when your application has a locale scheme of its own:
use Fkrzski\LaravelSteamApiSdk\Contracts\SteamLanguageResolver;
$this->app->bind(SteamLanguageResolver::class, TenantLanguageResolver::class);Timeouts and retries
Section titled “Timeouts and retries”The http block sets how long the connector waits on Steam and how many times it tries,
each option from its own variable:
STEAM_API_CONNECT_TIMEOUT=3STEAM_API_REQUEST_TIMEOUT=60STEAM_API_RETRY_TRIES=3STEAM_API_RETRY_INTERVAL=500STEAM_API_RETRY_EXPONENTIAL_BACKOFF=true| Variable | Unset | Meaning |
|---|---|---|
STEAM_API_CONNECT_TIMEOUT |
10, Saloon’s |
seconds to connect, 0 waits forever |
STEAM_API_REQUEST_TIMEOUT |
30, Saloon’s |
seconds for the whole request, 0 waits forever |
STEAM_API_RETRY_TRIES |
1 |
attempts in all, so 1 never retries |
STEAM_API_RETRY_INTERVAL |
0 |
milliseconds between attempts |
STEAM_API_RETRY_EXPONENTIAL_BACKOFF |
false |
doubles the interval after each retry |
Only what can change by the next attempt is retried: Steam unreachable, a timeout, a 5xx.
A 4xx and a spent rate limit are thrown at once, and so is the 500
sdrConfig() answers an unknown app with.
Once the tries are spent, a request Steam never answered raises SteamConnectionException
and a 5xx the root SteamApiException. Requests sent through
pool() go out once.
A value that is not a number, or is out of range, raises InvalidSteamHttpOptionException
naming the key. Like a rejected language code, it surfaces on your first Steam call and
renders as a 500.
Sender
Section titled “Sender”Requests go out through Guzzle until STEAM_API_SENDER names a class implementing
Saloon\Contracts\Sender. The container builds it each time the connector is built, so a
binding of that class decides how: a singleton is shared, and an unbound class is built anew.
That is where Guzzle middleware goes, since Steam::connector()->sender() returns the base
SDK’s SteamSender wrapper rather than a GuzzleSender. Give the sender a class of its own and
bind it with the middleware attached:
namespace App\Http;
use Saloon\Http\Senders\GuzzleSender;
final class SteamGuzzleSender extends GuzzleSender {}use App\Http\SteamGuzzleSender;use GuzzleHttp\Middleware;use Psr\Http\Message\RequestInterface;
public function register(): void{ $this->app->singleton(SteamGuzzleSender::class, fn (): SteamGuzzleSender => new SteamGuzzleSender()->addMiddleware( Middleware::mapRequest( static fn (RequestInterface $request): RequestInterface => $request->withHeader('User-Agent', 'my-app/1.0'), ), ));}STEAM_API_SENDER=App\Http\SteamGuzzleSenderA sender built on another client has to read connect_timeout and timeout off the request,
which is how the timeouts reach it —
What a sender has to honour
lists the rest.
The class name is normalised to the one it is declared with, so a leading \ or another casing
still reaches the binding. A value naming no sender class raises InvalidSteamHttpOptionException
on your first Steam call, rendered as a 500, and a binding that hands back something other than a
Sender raises UnexpectedValueException.
Nightwatch
Section titled “Nightwatch”With Laravel Nightwatch installed and enabled, the default
Guzzle reports every Steam request to it as an outgoing request, which saloonphp/laravel-plugin
cannot do, since the base SDK wraps every sender. NIGHTWATCH_ENABLED=false leaves plain Guzzle.
A sender of your own is left as it is, so it reports only once Nightwatch’s middleware is attached to it:
use App\Http\SteamGuzzleSender;use Laravel\Nightwatch\Facades\Nightwatch;
$this->app->singleton(SteamGuzzleSender::class, fn (): SteamGuzzleSender => new SteamGuzzleSender() ->addMiddleware(Nightwatch::guzzleMiddleware()));Whichever sender carries a request, the API key is stripped from every Steam URL Nightwatch records.
Route binding
Section titled “Route binding”The route_binding block resolves a route parameter straight into a SteamId. It
is off by default — the parameter name is claimed application-wide. Set
enabled to true to opt in; Route binding
has the full guide.
The connector binding
Section titled “The connector binding”The provider binds SteamConnector as a scoped instance: one per request, torn
down with it. Under Laravel Octane and between
queue jobs the container outlives the request, and a shared connector would carry
its API key into the next one.
It resolves lazily, so a misconfigured option surfaces on your first Steam call rather
than at boot. Reach it through
Steam::connector() — for the
current request only.
Contracts
Section titled “Contracts”Contracts\SteamManager is what the Steam facade resolves — type-hint it to inject the
manager instead of reaching through the facade:
use Fkrzski\LaravelSteamApiSdk\Contracts\SteamManager;
final class PlayerController{ public function __construct(private readonly SteamManager $steam) {}}The contract and the concrete SteamManager resolve to one scoped instance, so binding
either swaps what the facade hands back — a decorator wrapping the shipped manager, say.
Contracts\SteamIdBinder is the same seam for the
route binding, and
Contracts\SteamLanguageResolver for the locale mapping. Every
class the package ships is final; the container is the extension point.
Rate limiting
Section titled “Rate limiting”The rate_limit block sets the cache store the request counters live in, the limits
replacing Steam’s 100 000 a day, and the cooldown after a 429.
Rate limiting has the full guide, queued jobs included.
Exception rendering
Section titled “Exception rendering”A Steam failure nothing in the application caught renders as the status it maps to rather than a blanket 500 — the guide has the table. Turn the group off to render it yourself:
'exceptions' => [ 'render' => false,],The flag does not reach misconfiguration. A rejected API key, a missing one, a batch over 100 IDs, and a language code, HTTP option, rate limit option or logging option the config cannot use are a 500 whatever it is set to: Steam answers a bad key with a 400 or a 403, and passing that on would blame the caller for something only you can fix.
Logging
Section titled “Logging”The logging block logs Steam traffic to a channel of yours. It is off by default — set
STEAM_API_LOG_CHANNEL to opt in, and STEAM_API_LOG_RESPONSES to log every response too;
Logging has the full guide.
Checking your setup
Section titled “Checking your setup”The provider registers a Steam API section on Laravel’s about command:
php artisan about --only=steam_api Steam API ............................................................. 429 Cool-down ........................................... 60s (default) API Key .................................................. ********4f2a Language .......................................... polish (locale: pl) Logging ........................................... steam (+ responses) Rate Limit Store ................................................ redis Requests Remaining . 99,412 of 100,000 per day, 9 of 10 per 1s (sleeps) Retries ............................. 3 attempts, 500ms apart, doubling Route Binding ....................................... enabled (steamId) Sender .................................. Guzzle + Nightwatch (default) Timeouts ...................................... connect 3s, request 60s- API Key — masked to its last four characters, so the output is safe to paste
into a bug report.
NOT SETwhen no key is configured. - Language — the code every localised request carries, and which of the two answers it
is:
polish (config)orpolish (locale: pl).NOT SETnames its source the same way, andINVALIDstands alone, since only config can carry a rejected code. - Logging —
disableduntil a channel is set, then its name, with(+ responses)once every response is logged too. - Route Binding —
disabled, orenabledwith the parameter name it claims. - Timeouts — the connect and request timeouts, Saloon’s own marked
(default), andunlimitedfor0. - Retries —
none (default)for a single attempt, otherwise the attempts and the pause between them. - Sender — the class
STEAM_API_SENDERnames, otherwiseGuzzle (default), orGuzzle + Nightwatch (default)while Nightwatch is reporting.
The timeout, retry, sender and logging rows read INVALID for a value the connector does not
take. The three rate limit rows are covered in
Rate limiting.
Nothing here runs on boot: the values are computed when the command renders, and no request is sent to Steam.