I’m in Hebbs Cross, NS, Canada 🇨🇦  |  ☁️ It’s 13° at 9:26pm

Screenshot of Mastodon update with media attached

Using PHP and cURL to post media to the Mastodon API

A couple of years ago I posted the most popular article on the site on how to use PHP and cURL to post to the Mastodon API. Since then I’ve been meaning to post a follow-up -on how to post media and finally it’s here.

If you haven’t read the first part of this series and don’t know how to post to the Mastodon API, you should read that first. There’s a link back to this article at the end of that one.

Hopefully the code below is commented enough to make sense. It’s a bit tricky with sending using multipart-form-data and its boundaries.

$media_sleep = false;
$image = "test.jpg";
$alt_text = "An amazing test picture of a sunset";

// the main status update array, this will have media IDs added to it further down
// and will be used when you send the main status update using steps in the first article
$status_data = array(
  "status" => $status_message,
  "language" => "eng",
  "visibility" => "public"

// if we are posting an image, send it to Mastodon
// using a single image here for demo purposes
if ($image !== "") {
  // enter the alternate text for the image, this helps with accessibility
  $fields = array(
    "description" => $alt_text

  // get location of image on the filesystem
  $imglocation = "/path/on/server/to/$image";

  // add images to files array, this is a single image for demo
  $files = array();
  $files[$image] = file_get_contents($imglocation);

  // make a multipart-form-data delimiter
  $boundary = uniqid();
  $delimiter = '-------------' . $boundary;

  $post_data = '';
  $eol = "\r\n";

  foreach ($fields as $name => $content) {
    $post_data .= "--" . $delimiter . $eol . 'Content-Disposition: form-data; name="' . $name . "\"" . $eol . $eol . $content . $eol;

  foreach ($files as $name => $content) {
    $post_data .= "--" . $delimiter . $eol . 'Content-Disposition: form-data; name="file"; filename="' . $name . '"' . $eol . 'Content-Transfer-Encoding: binary' . $eol;
    $post_data .= $eol;
    $post_data .= $content . $eol;

  $post_data .= "--" . $delimiter . "--".$eol;

  $media_headers = [
    'Authorization: Bearer [YOUR ACCESS TOKEN GOES HERE]',
    "Content-Type: multipart/form-data; boundary=$delimiter",
    "Content-Length: " . strlen($post_data)

  // send the image using a cURL POST
  $ch_media_status = curl_init();
  curl_setopt($ch_media_status, CURLOPT_URL, "[YOUR MASTODON INSTANCE URL]/api/v2/media");
  curl_setopt($ch_media_status, CURLOPT_POST, 1);
  curl_setopt($ch_media_status, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch_media_status, CURLOPT_HTTPHEADER, $media_headers);
  curl_setopt($ch_media_status, CURLOPT_POSTFIELDS, $post_data);
  $media_response = curl_exec($ch_media_status);
  $media_output_status = json_decode($media_response);
  $media_info = curl_getinfo($ch_media_status);
  curl_close ($ch_media_status);

  // check the return status of the POST request
  if (($media_info['http_code'] >= 200) && ($media_info['http_code'] < 400)) {
    // check to see if the blurhash property has been set to know the image has been uploaded
    if (!empty($media_output_status->blurhash)) {
      // This is where you update your main status array (set above) with an array of media IDs
      $status_data['media_ids'] = array($media_output_status->id);

      // make sure to sleep further down
      $media_sleep = true;
    else {
      $post_error_message = "Error posting media file";
  else {
    $post_error_message = "Error posting media file, error code: " . $media_info['http_code'];

// the Mastodon instance needs to complete processing the media before it can be attached
// 3 seconds is a guess, YMMV on your instance
if ($media_sleep) {

// continue with posting the update using steps and from first article
// ...

At this point your media has been uploaded successfully and you have the list of media ID’s to send as part of the main status update covered in the first article. In that article, you’ll be sending another cURL request following those steps, except your $status_data array will have a media ID’s item in it.

One trick I learned is when sending a status update that contains media ID’s, use JSON to send the data instead of the default multipart-form-data. So from the first article you should change the headers and the status_data like this, *before* sending the cURL request:

// add a JSON content type to the headers
$headers = [
  'Authorization: Bearer [YOUR ACCESS TOKEN GOES HERE]',
  'Content-Type: application/json'

// JSON-encode the status_data array
$post_data = json_encode($status_data);
$ch_status = curl_init();
curl_setopt($ch_status, CURLOPT_URL, "[YOUR MASTODON INSTANCE URL]/api/v1/statuses");
curl_setopt($ch_status, CURLOPT_POST, 1);
curl_setopt($ch_status, CURLOPT_POSTFIELDS, $post_data); // send the JSON data
curl_setopt($ch_status, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch_status, CURLOPT_HTTPHEADER, $headers);

$output_status = json_decode(curl_exec($ch_status));
curl_close ($ch_status);

I hope this helps you sending status updates with media to the Mastodon API.