kertz/twitteroauth
PHP Twitter OAuth library for authenticating with Twitter and making API requests via OAuth 1.0a. Provides a simple client for posting tweets, reading timelines, and accessing REST endpoints, with support for media uploads and error handling. Suitable for server-side integrations.
kertz/twitteroauth is a maintained fork of Abraham Williams' OAuth 1.0a client. It is a plain Composer library — no service provider, no facade, no config file — so the first job in a Laravel app is wrapping it.
composer require kertz/twitteroauth
config/services.php (env-backed, never hard-coded):'twitter' => [
'consumer_key' => env('TWITTER_CONSUMER_KEY'),
'consumer_secret' => env('TWITTER_CONSUMER_SECRET'),
'access_token' => env('TWITTER_ACCESS_TOKEN'),
'access_token_secret' => env('TWITTER_ACCESS_TOKEN_SECRET'),
],
Get these from the Twitter/X developer portal. The app's access level must be Read and Write, and you must regenerate the access token/secret after changing that setting — old tokens keep their old permissions.
Look first at Abraham\TwitterOAuth\TwitterOAuth: get(), post(), upload(), getLastHttpCode(), getResponse(). That is 90% of the surface area.
First use case — verify credentials and post a tweet:
use Abraham\TwitterOAuth\TwitterOAuth;
$conn = new TwitterOAuth(
config('services.twitter.consumer_key'),
config('services.twitter.consumer_secret'),
config('services.twitter.access_token'),
config('services.twitter.access_token_secret'),
);
$me = $conn->get('account/verify_credentials');
if ($conn->getLastHttpCode() !== 200) {
throw new RuntimeException("Twitter auth failed: {$conn->getLastHttpCode()}");
}
$conn->post('statuses/update', ['status' => 'Hello from Laravel']);
The critical habit: always check getLastHttpCode(). The library does not throw on API errors (only on transport failures), so a failed post returns a decoded error object silently.
Bind a singleton in a service provider. For single-account apps, one connection per request is enough; resolve it from the container so tests can swap it.
$this->app->singleton(TwitterOAuth::class, fn () => new TwitterOAuth(
config('services.twitter.consumer_key'),
config('services.twitter.consumer_secret'),
config('services.twitter.access_token'),
config('services.twitter.access_token_secret'),
));
Wrap it in a domain service, not raw calls in controllers. Typed methods (tweet(string $status): string, uploadMedia(string $path)) keep API paths in one place and make v1.1 → v2 migration a single-file change later.
Post in a queued job. Twitter calls are network-bound and rate-limited; putting them in ShouldQueue jobs with $tries = 3, $backoff = 60 gives you retries without blocking the request. The kertz fork also has setRetries() with backoff for 5xx responses — useful, but it doesn't cover 429 rate limits.
Throttle explicitly. The v1.1 limit for most write endpoints is 15 requests / 15 minutes per user. Use Cache::lock() or RateLimiter::for('twitter', ...) before dispatching a burst, otherwise you'll get 429s that look like auth problems.
Cache read endpoints. account/verify_credentials, follower counts, and similar GETs are cheap to cache:
$profile = Cache::remember('twitter.me', now()->addMinutes(10), fn () =>
app(TwitterOAuth::class)->get('account/verify_credentials')
);
Media upload is a two-step flow. upload() uses multipart, then you attach the returned media_id_string in a normal post():
$media = $conn->upload('media/upload', ['media' => storage_path("app/{$path}")]);
$conn->post('statuses/update', [
'status' => $text,
'media_ids' => $media->media_id_string,
]);
Multi-account support breaks the singleton. Bind a factory instead (TwitterClientFactory::for($account) calling setOauthToken($token, $secret)), and pass the account into the job payload.
Testing: extract an interface or use Laravel's Http fakes is not possible here (the library owns its HTTP layer). The practical approach is to bind a fake subclass of TwitterOAuth that overrides get()/post() and records calls, or bind a wrapper interface and fake that.
/2/ (the modern API) are not supported by the request signing this library implements. Any new integration should seriously consider whether the library targets the endpoints you're allowed to use at all.getLastHttpCode() returning 0 means the request never reached Twitter (DNS, TLS, timeout). Don't confuse it with a 4xx. Wrap in try/catch for Abraham\TwitterOAuth\TwitterOAuthException too.x-rate-limit-reset from getLastXHeaders() before retrying.setDecodeJsonAsArray(true) makes responses arrays instead of stdClass — pick one style and set it in your service constructor so downstream code doesn't fork.setTimeouts($connect, $total) exists; defaults are generous. Set something like (5, 10) so a hung request doesn't hold a queue worker.strlen(). 280 "weighted" characters; URLs count as 23, CJK counts double. Use a library like twitter-text-php if you validate before posting, or you'll pass validation and still get a 403.report() context.TwitterOAuth and calling setApiVersion(), setOauthToken(), setRetries(), setDecodeJsonAsArray(), setTimeouts(). There is no middleware/hook system; if you need request signing changes or v2 support, you're better off with a different client.How can I help you explore Laravel packages today?