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

Twitter Laravel Package

endroid/twitter

Endroid Twitter is a lightweight PHP library that simplifies requests to the Twitter API using Abraham TwitterOAuth. Install it with Composer, then use your consumer keys to retrieve timelines or post status messages with optional media attachments.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and confirm the transitive dependency.

composer require endroid/twitter

This pulls in abraham/twitteroauth (the package is a thin wrapper around it) plus its Guzzle/curl requirements. Before writing a line of code, run composer show abraham/twitteroauth and note the resolved version — you will need that pin later (see Gotchas).

2. Get credentials. Register an app at the Twitter developer portal and collect all four values:

  • API key / consumer key
  • API secret
  • Access token
  • Access token secret

Note that the README example only shows the consumer key and secret being passed to TwitterOAuth. In practice TwitterOAuth takes four arguments, and any user-context call (statuses/home_timeline, statuses/update, media upload) needs the access token pair. The truncated README is misleading here — use all four.

3. Store them in config, not in code. In config/services.php:

'twitter' => [
    'consumer_key'    => env('TWITTER_CONSUMER_KEY'),
    'consumer_secret' => env('TWITTER_CONSUMER_SECRET'),
    'access_token'    => env('TWITTER_ACCESS_TOKEN'),
    'access_secret'   => env('TWITTER_ACCESS_TOKEN_SECRET'),
],

4. Bind the client once in a service provider so you never new Client(...) in a controller:

// App\Providers\TwitterServiceProvider
$this->app->singleton(\Endroid\Twitter\Client::class, function () {
    $c = config('services.twitter');

    return new \Endroid\Twitter\Client(
        new \Abraham\TwitterOAuth\TwitterOAuth(
            $c['consumer_key'],
            $c['consumer_secret'],
            $c['access_token'],
            $c['access_secret']
        )
    );
});

5. Where to look first. vendor/endroid/twitter/src/Client.php is the whole package — it is small. Read it end to end before using it; the README covers roughly two methods and the rest of the surface is whatever that one class exposes. Confirm (a) the constructor signature, (b) which methods return arrays vs. stdClass, and (c) whether errors are thrown or silently returned.

6. First use case. A cron-scheduled "post to the company account" command is the lowest-risk entry point — one write call, no pagination, no user context ambiguity:

$client->postStatus('Deployed v2.4.0 to production');

Then move to reading (getTimeline(50)) once writes are confirmed working.

Implementation Patterns

Wrap it in your own contract before you use it anywhere. This is the single highest-value pattern for this package, because it is archived and because Abraham\TwitterOAuth\TwitterOAuth does raw curl — Http::fake() in Laravel cannot intercept it. Define:

interface TwitterGateway
{
    public function postStatus(string $text, array $mediaPaths = []): string; // returns tweet id
    public function timeline(int $limit): array;                             // normalized DTOs
}

Bind EndroidTwitterGateway (delegating to Endroid\Twitter\Client) in production and a FakeTwitterGateway in tests. This also gives you a one-file migration path to the API v2 SDK later.

Always post from a queued job, never inline. A tweet is a network call to a third party with real rate limits; putting it in the request cycle means a 429 or timeout becomes a 500 for your user.

class PublishTweet implements ShouldQueue
{
    public int $tries = 3;
    public int $backoff = 60;   // 1m, 2m, 4m

    public function handle(TwitterGateway $twitter): void
    {
        $twitter->postStatus($this->text, $this->media);
    }

    public function retryUntil() { return now()->addHours(2); }
}

Use backoff generously — retrying a 429 in the same second just burns your quota again.

Keep the media flow in one place. The two-step sequence is: upload each file, collect media IDs, then create the status referencing them. If Client::postStatus takes an array of paths (as the README shows), make sure it uploads everything first and only then posts — a partial upload followed by a status call yields an orphaned media ID that expires unused. Cap at 4 images / 1 video and validate MIME type and size in a FormRequest before the job is dispatched, so bad input fails at the edge instead of in a retry loop.

Cache timeline reads aggressively. Home-timeline and user-timeline endpoints are the ones you'll hit most and they have the tightest budgets. Cache the normalized result for 1–5 minutes:

$tweets = Cache::remember("tw:timeline:{$userId}", 120, fn () =>
    $gateway->timeline(50)
);

Never call getTimeline() during a page render without a cache in front of it.

Normalize the response at the boundary. TwitterOAuth returns stdClass graphs whose shape differs from the v2 API and contains far more than you need. Map to a small DTO (id, text, createdAt, authorHandle, mediaUrls) inside the gateway so your views, API resources, and tests are insulated from both the package and the eventual migration.

Fire a domain event after success. TweetPublished($id) lets you decouple notifications, analytics, and Slack cross-posting from the publishing job — and gives you a clean place to store the returned tweet ID against your own model for later deletion or metrics.

For account-level, read-only features, consider app-only auth. Public timeline reads can use a bearer token; user-context tokens are only required for posting, DMs, and private timeline reads. If all you do is read public data, use a separate client instance with a bearer token so a leaked write token isn't in that code path.

Gotchas and Tips

This package is archived — treat it as a vendored dependency. No fixes will land. Pin both layers in composer.json ("endroid/twitter": "x.y.z" and an explicit abraham/twitteroauth constraint) and commit composer.lock. If the wrapper's constraint on abraham/twitteroauth is a loose ^, a composer update can silently resolve to a major version whose method signatures no longer match what Client calls — that's a runtime fatal, not a build failure.

It is Twitter API v1.1 only. There is no v2 support and never will be. Most v1.1 read endpoints require elevated access that new apps don't get, and the free tier's write quota is very small (order of tens of posts per month, not per day). Verify your app's actual tier before building features on top of this, and design the queue to be quota-aware rather than retry-blind.

Check the HTTP status yourself. TwitterOAuth does not throw on API errors; it returns a body that may contain an errors array while the call "succeeds." In the gateway, inspect $twitterOAuth->getLastHttpCode() and the decoded body, and throw a typed exception (RateLimitedException, DuplicateTweetException) so the queue can distinguish retryable from terminal failures.

Duplicate status (code 187) is not a retry problem — it's a bug in your job. If retries produce "Status is a duplicate," your job is not idempotent: the first attempt succeeded but you lost the response. Guard with a tweeted_at timestamp on your model and a ShouldBeUnique job, and treat 187 as success on retry.

Don't rely on env() outside config files. Once you run php artisan config:cache, any env('TWITTER_...') call in a provider or job returns null — a very common cause of "works locally, 401 in production." Route everything through config('services.twitter.*').

Rate-limit headers are your friend. v1.1 responses expose x-rate-limit-remaining and x-rate-limit-reset. Log them (or stash them in the cache) after every call; when remaining hits 0, schedule the next job for reset rather than letting it 429. This is much cheaper than debugging a wall of failed jobs.

Media upload quirks to plan for: uploads are chunked for larger files and can fail midway; a failed upload still consumes quota; media IDs expire if not attached to a status within a short window; and alt text requires an extra metadata call that this wrapper almost certainly does not expose — you'll need to drop to the underlying TwitterOAuth instance via a getter or your own subclass for that.

Extension points are thin. There is no Laravel service provider, facade, or config file shipped. Subclassing Endroid\Twitter\Client and overriding the request method is the practical seam for adding logging, metrics, and error translation; if you need more than that, fork the single class into app/Services/Twitter and drop the Composer dependency — for a package this size, that's often less work than fighting a dead upstream.

Testing: never let the test suite make a live call. Bind a fake implementation of TwitterGateway in TestCase::setUp() and assert on the recorded calls. If you must test the real client, only the constructor and response-mapping logic are safe to unit test with a hand-built stdClass fixture.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource