atymic/twitter
atymic/twitter is a PHP client for Twitter API v2 with Laravel integration. It provides a clean, expressive interface for tweets, users, timelines, likes, and OAuth authentication, making it easy to add Twitter-powered features to PHP applications.
This is a Laravel wrapper around the Twitter/X API (v2 plus selected v1.1 endpoints). It's the fastest path from a Laravel app to posting tweets, uploading media, and pulling user/tweet data without hand-rolling OAuth.
composer require atymic/twitter:^3.4php artisan vendor:publish --provider="Atymic\Twitter\ServiceProvider".env:
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_TOKEN_SECRET=
TWITTER_BEARER_TOKEN=
use Atymic\Twitter\Facade\Twitter;
Twitter::tweet('Hello from Laravel');
Start with read-only calls (getUserByUsername, getTweet) using the bearer token to validate credentials, then add the OAuth 1.0a user tokens for anything write-related. Read the README's method list first — the facade proxies to endpoint methods, so it's the de facto API reference.
First real use case: a scheduled/queued job that posts a status update or announcement from your app.
3.4.0 notes: this is a compatibility release — Carbon 3, Laravel 12, and Laravel < 10 is now dropped. New installs are unaffected; existing Laravel 9 or older apps must stay on 3.3.x.
Wrap it in a service, don't call the facade from controllers. Twitter calls are slow, rate-limited, and fail in ways that need retries. Put posting behind a small TwitterService or a queued job so controllers stay thin and failures are retryable.
class PublishTweet implements ShouldQueue
{
use InteractsWithQueue, Queueable;
public int $tries = 3;
public array $backoff = [30, 120, 600];
public function __construct(public string $text) {}
public function handle(): void
{
Twitter::tweet($this->text);
}
}
Media + tweet. Media upload still goes through the v1.1 chunked upload endpoint, which returns an ID string you then attach to the v2 tweet call:
$media = Twitter::uploadMedia(['media' => $path]);
Twitter::tweet('Look at this', ['media_ids' => [$media->media_key ?? $media->media_id_string]]);
The exact return shape differs by endpoint version — log the raw response once when integrating so you know which key to read.
Threads are built by chaining replies; keep the tweet IDs in an array and pass the previous ID as the reply parameter. Don't fire them concurrently — the ordering guarantee comes from doing them sequentially.
Token strategy. Bearer token = app-only auth, reads only. Posting, liking, DMing, and media upload require the OAuth 1.0a quartet (consumer key/secret + access token/secret) with the correct user context. If writes fail with a 403 while reads succeed, that's the split, not a bug.
Testing. The client sits on Laravel's HTTP layer, so Http::fake() works for unit tests. For higher-level tests, mock your TwitterService rather than the facade, and assert on intent.
Config caching. If you use config:cache in deploy, add credential rotation to your deploy checklist — cached config ignores .env changes silently.
^3.3 if you're stuck on Laravel 9, and don't let an automatic minor-version bump pull 3.4 in on those projects.diffIn* methods return floats rather than rounded integers and are signed by default. If you store or compare timestamps returned from tweet/user payloads, re-check anything that feeds into expiry logic or ordering.true for success. Some endpoints return partial objects on soft failures. Check for the presence of the expected ID before marking a job complete.$this->release($seconds) rather than sleeping in the worker.php artisan queue:restart is part of credential rotation.try/catch blocks across call sites.php artisan config:clear first, then php artisan tinker a single getUserByUsername('twitter') call. One successful read isolates whether the problem is credentials, quotas, or your write path.How can I help you explore Laravel packages today?