williamsdb / php2bluesky
A library that makes posting to Bluesky from PHP simple.
Requires
- php: ^8.0
- ext-dom: *
- ext-gd: *
- cjrasmussen/bluesky-api: ^2.0
README
php2Bluesky
A simple library that allows posting to Bluesky via the API.
Table of Contents
About The Project
With all the uncertainty surrounding the future of X (née Twitter), I decided to take a look at Bluesky, which somewhat ironically has its roots in Twitter, where it was started as an internal project. I worry about Bluesky's long-term, given that ultimately it too has to make money, something that Twitter has singularly failed to do. None of this, of course, affects the topic today, which is posting to Bluesky via the API.
I needed a way to post to Bluesky from PHP and so I searched for a library to help and when I couldn't find one I wrote this.
Built With
Getting Started
NOTE: This is for v2 of php2Bluesky. If you are looking for the v1 details you can find that here.
Running the script is very straightforward:
- install composer
- add the BlueskyAPI
composer.phar require cjrasmussen/bluesky-api
- add php2Bluesky
composer.phar require williamsdb/php2bluesky
Now you can inspect example.php to get some examples and/or see below.
If you are interested in what is happening under the hood then read this series of blog posts.
Prerequisites
Requirements are very simple, it requires the following:
- PHP (I tested on v8.1.13) - requires php-dom and php-gd
- Clark Rasmussen's BlueskyApi (requires v2 or above)
- a Bluesky account and an Application Password (see this blog post for details of how to do that)
- ffprobe if you intend to upload videos (optional).
Read more about the requirements for video upload here.
Installation
- As above
Usage
Here's a few examples to get you started.
Note: connection to the Bluesky API is made via Clark Rasmussen's BlueskyApi which this makes a connection to Bluesky and manages tokens etc. See here for more details.
Setup and connect to Bluesky
require __DIR__ . '/vendor/autoload.php'; use williamsdb\php2bluesky\php2Bluesky; $php2Bluesky = new php2Bluesky(); $handle = 'yourhandle.bsky.social'; $password = 'abcd-efgh-ijkl-mnop'; // connect to Bluesky API $connection = $php2Bluesky->bluesky_connect($handle, $password);
Overriding the defaults
When you instantiate php2Bluesky a number of defaults are set as shown in the table below. However, you can override these as follows:
$php2Bluesky = new php2Bluesky($linkCardFallback = 'RANDOM', $failOverMaxPostSize = FALSE, $randomImageURL = 'https://picsum.photos/1024/536', $fileUploadDir='/tmp' $defaultLang = ['fr']);
See details below on how to set these.
Name | Default values | Explanation |
---|---|---|
$linkCardFallback | 'BLANK' | What should happen if a linkcard doesn't have any associated image. Possibe values: RANDOM - a random image taken from $randomImageURL BLANK - a blank image ERROR - throw an error URL - the image at this location, URL=https://... |
$failOverMaxPostSize | FALSE | Throw an error if the text is longer than the allowed length of a post. |
$randomImageURL | 'https://picsum.photos/1024/536' | Where to source the random image. Use with $linkCardFallback above. |
$fileUploadDir | '/tmp' | Where to upload images to before posting them. Make sure that the process has permissions to write here. |
$defaultLang | ['en'] | The default languages of posts. This must be specified as an array. |
Sending text with tags
$text = "This is some text with a #hashtag."; $response = $php2Bluesky->post_to_bluesky($connection, $text); print_r($response); if (!isset($response->error)){ $url = $php2Bluesky->permalink_from_response($response, $handle); echo $url.PHP_EOL; }
Uploading a post with a single image and embedded url
$filename1 = 'https://upload.wikimedia.org/wikipedia/en/6/67/Bluesky_User_Profile.png'; $text = 'Screenshot of Bluesky'; $alt = 'This is the screenshot that Wikipedia uses for their https://en.wikipedia.org/wiki/Bluesky entry.'; $response = $php2Bluesky->post_to_bluesky($connection, $text, $filename1, '', $alt); print_r($response); if (!isset($response->error)){ $url = $php2Bluesky->permalink_from_response($response, $handle); echo $url.PHP_EOL; }
Uploading a post with multiple images (both local and remote)
$filename1 = 'https://upload.wikimedia.org/wikipedia/en/6/67/Bluesky_User_Profile.png'; $filename2 = '/Users/neilthompson/Development/php2Bluesky/Screenshot1.png'; $filename3 = 'https://www.spokenlikeageek.com/wp-content/uploads/2024/11/2024-11-18-19-28-59.png'; $filename4 = '/Users/neilthompson/Development/php2Bluesky/Screenshot2.png'; $text = 'An example of four images taken both from a local machine and remote locations with some alt tags'; // send multiple images with text $imageArray = array($filename1, $filename2, $filename3, $filename4); $alt = array('this has an alt', 'so does this'); $response = $php2Bluesky->post_to_bluesky($connection, $text, $imageArray, '', $alt); print_r($response); if (!isset($response->error)){ $url = $php2Bluesky->permalink_from_response($response, $handle); echo $url.PHP_EOL; }
Uploading a post with a single video
$filename1 = '/path/to/local/video.mp4'; $text = 'A beautiful video'; $response = $php2Bluesky->post_to_bluesky($connection, $text, $filename1); print_r($response); if (!isset($response->error)){ $url = $php2Bluesky->permalink_from_response($response, $handle); echo $url.PHP_EOL; }
Adding labels
Bluesky allows certain lables to be applied to posts to indicate whether they include any graphic content. There is a table below of what labels are supported but also see BlueskyConsts.php for the latest.
$response = $php2Bluesky->post_to_bluesky(connection: $connection, text: $text = "this is the text of your post", media: $media = "https://www.spokenlikeageek.com/wp-content/uploads/2025/06/SCR-20250628-jzfa.png", link: $link = '', alt: $alt = "Image of labels applied to a Bluesky post", labels: $labels = ["!no-unauthenticated", "porn", "sexual"]);
Label Value | UI Label | Explanation |
---|---|---|
porn | Adult | Explicit/erotic sexual content |
sexual | Suggestive | Mild or suggestive sexual themes |
nudity | Nudity | Non‑erotic or artistic nudity |
graphic-media | Graphic Media | Violent or graphic content |
!no-unauthenticated | n/a | makes the content inaccessible to logged-out users in applications which respect the label. |
Setting the language
By default the language of your posts is set to English but you can override this in one of two ways. Either specify the langauge when creating the instance of php2Bluesky (see above) or pass it when posting. It must be an array even if you are passing a single language.
$response = $php2Bluesky->post_to_bluesky(connection: $connection, text: $text = "Bonjour le monde!", media: $media = "", link: $link = "", alt: $alt = "", labels: $labels = "" lang: $lang = ["fr"]);
It is also possible to pass multiple languages as follows:
$response = $php2Bluesky->post_to_bluesky(connection: $connection, text: $text = "Bonjour le monde!".PHP_EOL."Hello World!", media: $media = "", link: $link = "", alt: $alt = "", labels: $labels = "" lang: $lang = ["fr", "en-GB"]);
Known Issues
See the open issues for a full list of proposed features (and known issues).
Contributing
Thanks to the follow who have provided techincal and/or financial support for the project:
- Jan Strohschein
- Ludwig Noujarret
- Paul Lee
- AJ
- https://bsky.app/profile/bobafettfanclub.com
- Doug "Bear" Hazard
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
License
Distributed under the GNU General Public License v3.0. See LICENSE
for more information.
Contact
Bluesky - @spokenlikeageek.com
Mastodon - @spokenlikeageek
X - @spokenlikeageek
Website - https://spokenlikeageek.com
Project link - Github