X for PHP: a small client library for the X API v2
Small and easy PHP library for sending messages to Twitter and receiving statuses.
At a glance
- What is it?
- The dg/twitter-php library, now published under the `DG\X` namespace, wraps the X API v2 in a handful of PHP methods for posting, reading and following, and it keeps the old `DG\Twitter` class names as aliases so existing code still runs. It suits PHP developers who need a thin API client rather than a framework, and its usefulness is bounded by what the X API access tier allows.
- Who is it for?
- Adopt X for PHP when you need to post, read and follow from a PHP application with a thin, typed client and no framework around it, since the whole library is one client class, one exception class and a pass-through for any endpoint you need that it does not wrap.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 102 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A thin client, not an SDK with a framework around it
The library describes itself as a small and easy-to-use library for the X, formerly Twitter, API v2, and the README calls it small and easy-to-use. That framing is accurate, and it is the main thing to understand before you build on it.
There is one client class. You construct it with four credentials, and from there you have methods for the things an application typically does with a social account. Nothing else is required: no event system, no queue, no entity mapping layer, no dependency injection container.
use DG\X\Client;
$x = new Client($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);The four constructor arguments are the OAuth credentials you generate in the X Developer Portal, which the README walks through step by step. You create a developer account, and the free tier is stated to be sufficient for the basic setup. You then create a Project and an App, and the permissions step is called out as important: under App Settings and User authentication settings you set the App permissions to Read and Write, the Type of App to Web App, a placeholder Callback URI of `https://localhost/callback` that is not used, and any valid Website URL.
You then generate an API Key and Secret, and an Access Token and Secret, and the README carries a note that after changing permissions you must regenerate the Access Token. That is a detail that will bite you if you change permissions after the fact and then wonder why your writes fail with a permission error while everything else works.
The last step in the portal setup is a use case description, which X requires to be at least 250 characters, and the README supplies a sample describing a personal single-user tool for publishing one's own content, with no reading or analysis of other users' data and no automation of likes, retweets or follows. The sample reads as a template for the kind of description that passes review for a personal tool.
Posting: text, one image, several images
Sending is a single method, and the same method handles the media cases through an optional second argument.
The simplest call is a status text:
$x->sendTweet('I am fine today.');With a single image you pass the path as the second argument:
$x->sendTweet('Check this out!', '/path/to/image.jpg');And with several images you pass an array of paths:
$x->sendTweet('Photos!', ['/path/to/img1.jpg', '/path/to/img2.jpg']);The changelog records that multi-image upload was added back in v3.6 in 2016, and that v3.5 allowed a message starting with an @ sign together with a file upload, so the argument handling here is old and settled rather than a recent addition.
One naming quirk to note. The method is still called `sendTweet`, and the exception type is still `TwitterException` in the older namespace, even though the service was renamed. The v5.0 changelog explains why: the class names `DG\Twitter\Twitter`, `DG\Twitter\Exception`, `Twitter` and `TwitterException` remain available as aliases for `DG\X\Client` and `DG\X\Exception`. So the vocabulary in the API is a deliberate compatibility choice rather than an oversight, and existing code written against the old namespace keeps working.
The method names follow the same pattern across the library. The v5.0 changelog lists the clean API as `sendTweet()`, `deleteTweet()`, `getTweet()`, `getMyTweets()`, `getTimeline()`, `getMentions()`, `search()`, `getUser()`, `getUserById()`, `getFollowers()`, `follow()` and `sendDirectMessage()`.
Reading: your own tweets, a timeline, mentions, search and users
The read methods are equally direct. Your own tweets come from `getMyTweets()`, the home timeline of the people you follow from `getTimeline()`, and mentions from `getMentions()`.
$tweets = $x->getMyTweets();$tweets = $x->getTimeline();Search takes a query string, and the README's example is a hashtag:
$results = $x->search('#php');User lookups come in two forms, by screen name and by numeric id, and there is a separate method for followers:
$user = $x->getUser('elonmusk');
$user = $x->getUserById('44196397');
$followers = $x->getFollowers('44196397');Social actions are equally compact, one call to follow a user id and one to send a direct message:
$x->follow('44196397');
$x->sendDirectMessage('44196397', 'Hello!');There is also a static helper for presentation, `Client::clickable()`, which makes links, mentions and hashtags in tweets clickable. The README shows it applied to a list of tweets:
foreach ($tweets as $tweet) {
echo Client::clickable($tweet);
}The changelog shows this helper has been adjusted over the years to work with status objects rather than plain text, to support media, and in v3.8 to add support for the `full_text` field, which is the field X uses for text that exceeds the short post length.
And there is a credential check, `authenticate()`, which returns a boolean you can branch on:
if (!$x->authenticate()) {
die('Invalid credentials');
}That method is also the one the README ties to the access tier discussion, since it is listed among the endpoints the free tier permits.
The access tier is the real constraint on this library
The most important line in the README is the note about tiers. The Free tier of the X API only allows sending and deleting tweets and reading your own profile, through `authenticate()`. Reading timelines, searching, user info, followers and other read endpoints require the Basic tier or higher.
That single sentence reframes the whole library. A team on the free tier gets a working posting client and little else, so `getTimeline()`, `search()`, `getUser()` and `getFollowers()` will not return data for them. The methods exist because the API has them, not because every caller can use them.
The API itself has moved more than once, and this is where the library's history shows. Version 3.0 in December 2012 updated to Twitter API 1.1, and version 2.0 in August 2012 had already added support for the OAuth authentication protocol and the `clickable()` helper. The current version 5.0, dated 2/2026, uses X API v2 endpoints, moved to a new `DG\X` namespace with a `Client` class, simplified inline OAuth 1.0a, and replaced raw CURL with the Guzzle HTTP client.
That last change is the one that matters for an operator. The changelog for v4.1 records that the token is optional throughout plus a supply `get()` method, and the library now performs its HTTP through Guzzle rather than cURL directly. The v3.1 entry mentions that `Twitter::$httpOptions` was added for custom cURL configuration, and that escape hatch disappeared with the switch to Guzzle, so anyone who was tuning the HTTP layer at that level has to find the equivalent in the Guzzle configuration instead.
The security posture in the changelog is also worth noting for anyone handling credentials. Version 3.2 in January 2014 changed the Twitter API to use SSL OAuth.
Raw requests and error handling
Two features make the library more than a fixed set of wrappers.
The first is the raw request method. You can call any X API v2 endpoint directly:
$result = $x->request('tweets', 'POST', ['text' => 'Hello from raw API!']);
$result = $x->request('users/me', 'GET', ['user.fields' => 'description,profile_image_url']);The signature is a path, an HTTP method and a parameter array, and the parameter names are the query string keys the API expects, such as `user.fields`. This matters more than it first appears. The X API v2 is a moving target, and a client that only exposes twelve methods will be missing whatever the API adds next. With `request()` in hand, the wrapper methods are conveniences and the API is the contract, so a new endpoint is a one-line change rather than a library update.
The second is error handling, and the rule is simple: all methods throw `DG\X\Exception` on error.
try {
$x->sendTweet('Hello!');
} catch (DG\X\Exception $e) {
echo 'Error: ', $e->getMessage();
}One exception type for everything means you cannot distinguish a rate limit from a permissions problem from a bad request by catching different classes. You read the message. For a library of this size that is a reasonable trade, and it keeps the API surface small, but it does mean an application that wants to retry on one class of failure has to inspect the exception rather than catch a narrower type.
The repository layout supports all of this being a maintained library rather than a script. There is a `src/` directory, a `tests/` directory, a `composer.json`, a `phpstan.neon` and an `AGENTS.md` file. The changelog states PHPStan level 8 and Nette Tester tests, so the code is statically analysed at a strict level and the test suite uses the Nette testing framework, and the homepage is nette.org, which is the framework author's own site.
Where X for PHP is the wrong tool
The first wrong-tool case is a PHP application that needs streaming or real-time updates. The client exposes request-response calls to the API and nothing that holds a connection open, so a live timeline, a filtered stream or an event-driven pipeline is out of scope for this library.
The second is anything that needs the endpoints above the free tier, or the enterprise features of the API such as ads or account management. The library wraps the standard endpoints, and the README's tier note is explicit that reading timelines, searching and follower data require the Basic tier or higher. If your product depends on that data, the access tier is the gate, not the library.
The third is a team that wants a framework. There is no event system, no middleware stack, no job queue, no caching layer and no testing harness for HTTP responses. If your PHP application already runs on a framework that has an HTTP client and a queue, the value of a separate small client is mostly the OAuth handling and the type-safe method signatures, and you can get those from a hand-written Guzzle wrapper instead.
The fourth is a project that cannot accept a library with a single error type and no retry logic. A posting application that runs unattended will hit rate limits, and the library does not document any retry or backoff behaviour, so a production deployment has to add that around it.
The alternative worth naming is the X API's own HTTP interface used directly with Guzzle, which is what the library itself now does under the hood since v5.0. The difference in approach is that calling the endpoints yourself means owning OAuth 1.0a signing, the media upload step and the response shapes, while this library has already solved those, kept them working across the v1.1 to v2 migration, and given them method names that read like the platform's own vocabulary. For a one-off integration the direct route is fine. For a product that posts regularly, the library saves you the OAuth edge cases.
Maintenance, versioning and the licence
The release history has an unusual shape that is worth understanding before you depend on it. Version 5.0.0 was tagged on 2026-02-24, and before that the most recent tags are v3.7 from 2018-03-15 and v3.6 from 2016-08-15.
So the gap is nearly eight years, then a rewrite. The changelog explains the gap implicitly: v3.0 in December 2012 moved to Twitter API 1.1, and v5.0 in 2026 moved to X API v2 with a new namespace, Guzzle instead of cURL, simplified inline OAuth 1.0a and a PHP 8.2 requirement. Between the API generations there was a long period when the library was stable enough that it needed few releases, and then a period when the platform changed underneath it and the library had to be rebuilt rather than patched.
The last push to the default branch was on 2026-07-13, and the repository is not archived, so work continues on master even though the newest tag is from February. That gap between the tag and the branch head is the thing to watch: if you pin v5.0.0 you get the February state, and if you track master you get changes that are not covered by a release note.
The licence is BSD-3-Clause, with a `license.md` file in the tree and the README's badge pointing at it. The copyright line reads (c) David Grudl, 2008, 2026, which also explains the longevity of the project and the fact that the first release was v1.0 in July 2008. A permissive BSD licence with no copyleft obligation is a comfortable fit for a library that most people will vendor into commercial PHP applications. This is a description of the published terms, not legal advice.
The project is installed with `composer require dg/twitter-php` and requires PHP 8.2 or higher, which is the practical floor to check first, and it is distributed on Packagist under the same name.
Editorial conclusion
Adopt X for PHP when you need to post, read and follow from a PHP application with a thin, typed client and no framework around it, since the whole library is one client class, one exception class and a pass-through for any endpoint you need that it does not wrap. Do not adopt it expecting the free tier to give you timelines, search or follower data, because the README is explicit that those endpoints require the Basic tier or higher, and do not adopt it if your application needs real-time streaming, which this client does not expose. Before you write code against it, create the app in the X Developer Portal with Read and Write permissions, regenerate the access token after changing permissions, and write your use case description, which X requires to be at least 250 characters.
Frequently asked questions
How do I install X for PHP and create a client?
Install it with `composer require dg/twitter-php`, which requires PHP 8.2 or higher, then construct the client with your OAuth credentials: `new Client($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret)` using the `DG\X\Client` class.
What can I do with the free tier of the X API using this library?
The free tier only allows sending and deleting tweets and reading your own profile through `authenticate()`. Reading timelines, searching, user info, followers and other read endpoints require the Basic tier or higher, so methods like `getTimeline()` and `search()` will not return data on a free tier account.
How do I post a tweet with images using X for PHP?
Use the `sendTweet` method with a text argument and an optional media argument, which takes a single image path or an array of image paths. For example `$x->sendTweet('Photos!', ['/path/to/img1.jpg', '/path/to/img2.jpg']);`.
Can I call endpoints that the X for PHP client does not wrap?
Yes. The `request` method lets you call any X API v2 endpoint directly by path, HTTP method and parameter array, for example `$x->request('users/me', 'GET', ['user.fields' => 'description,profile_image_url'])`, so new endpoints do not require a library update.
What changed in version 5.0 of X for PHP?
The changelog lists a new `DG\X` namespace with a `Client` class, use of X API v2 endpoints, the Guzzle HTTP client replacing raw CURL, simplified inline OAuth 1.0a, a cleaned method list, PHPStan level 8, Nette Tester tests and a PHP 8.2 requirement. The old class names remain available as aliases.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dg-twitter-php)Was this useful?