Transitioning from Twitter API v1.1 to v2 (Part 2)

In the first part of this post, I showed how to set up your Twitter dev account ready for using v2 of the API. In this post, I will show you how to access that API and post a tweet.

Goodbye Codebird

I am moving from Codebird which has been great but only supports v1.1 of the API. I looked at all the PHP libraries that I could find and decided on TwitterOAuth which styles itself as the “most popular PHP library for use with the Twitter OAuth REST API”. I toyed with this package but couldn’t get it to work.

The first issue was that all the libraries required PHP v8.1 and my server was still on v7. This meant additional work as I had to update code for deprecated commands or mandatory parameters and the framework I use also needed updating. This added extra time and meant that my bot was offline longer than I would have liked.

But which keys should I use?

Getting TwitterOAuth setup was simple as I could use Composer to add it to my project. Working out which of the many keys Twitter offers you was less simple and the basic TwitterOAuth documentation wasn’t much help either.

I am assuming that you are using this to post from your own Twitter account rather than from some third party. If you need the latter then you will need to go through the “Connect with Twitter” loop which is not covered here.

As outlined in the previous post you will need API Key and Secret from the Consumer Keys section and Access Token and Secret from the Authentication Tokens section. This will allow you to access the API to both read and post Tweets as your own account.

Adding Media

There are two oddities with uploading media to Twitter. The first is that v2 of the Twitter API doesn’t (currently) support this and secondly TwitterOAuth doesn’t support uploading from anywhere but your local machine/server. I’d been used to Codebird which allowed for uploading from other locations such as from a URL which meant you could upload from S3, for example.

All this means that you need to switch to v1.1 to upload and then switch to v2 to actually Tweet – you cannot do both in v1.1 (or v2 presently). You also need to have somewhere to store any media temporarily before it is uploaded to Twitter. I created a folder called “cache” in the same place as my script – just make sure to give it write permissions.

Tweeting the Tweet

Now that the connection is made and the media is uploaded you can finally post your Tweet. You can simply do this by building a simple array containing either just the text you want to include (this can include URLs if you want to post a link and text) or a link to the media you uploaded.

The sample code below brings this all together to show you how easy it is. Ultimately this will move to my GitHub page. If you have any comments or questions please add a comment below.

Sample Code


    // load composer & libraries
    require __DIR__.'vendor/autoload.php';  

    // set connection variables
    $consumer_key = '<your consumer key>';
    $consumer_secret = '<your consumer secret>';
    $oauth_token = '<your oauth_token>';
    $oauth_token_secret = '<your oauth_token_secret>';        

    // upload media (image, video etc.)
    $connection = twitter_connect($consumer_key, $consumer_secret, $oauth_token, $oauth_token_secret, "1.1");
    $media = upload_media_to_twitter($connection, "<filename>");

    // tweet it
    $connection = twitter_connect($consumer_key, $consumer_secret, $oauth_token, $oauth_token_secret);
    $reply = post_to_twitter($connection, "this is a test with media upload", $media);

    // display the response so that you can see that it has worked

    // delete the file you uploaded


    function twitter_connect($consumer_key, $consumer_secret, $access_token, $access_token_secret, $api="2")

        // Setup TwitterOAuth
        $connection = new TwitterOAuth($consumer_key, $consumer_secret, $access_token, $access_token_secret);
        return $connection;

    function upload_media_to_twitter($connection, $filename)

        // check a file name has been passed
        if (empty($filename)) return;

        // copy the file from where it is held to local storage (this example assumes that it is held on my website)
        copy("".$filename, './cache/'.$filename);
        $media = $connection->upload('media/upload', ['media' => './cache/'.$filename]);
        return $media->media_id_string;


    function post_to_twitter($connection, $tweetText, $media='')

		// tweet it
		if (!empty($media)){

            $data = [
                "text" => html_entity_decode($tweetText, ENT_QUOTES),
                "media"=> [
                "media_ids" => [$media],
            $reply = $connection->post("tweets", $data, true);


            $data = ["text" => html_entity_decode($tweetText, ENT_QUOTES)];
            $reply = $connection->post("tweets", $data, true);


        return $reply;


    function delete_cached_file($filename)

		// check a file name has been passed
		if (empty($filename)) return;





Leave a Reply

Your email address will not be published. Required fields are marked *