Skip to main content

Uploading a Video to Bluesky: Sending a Video File, Managing the Process, and Creating a Post

· 5 min read
Eldar Shahmaliyev
Maintainer of BlueSky SDK

Uploading a video to Bluesky and sharing it in a post requires several steps. In this post, we will walk through the process using the Bluesky SDK, including:

  • Uploading a video file,
  • Managing the upload process,
  • Posting the uploaded video.

Media uploads on the Bluesky platform are handled through a job-based system. This means that after uploading a file, it may not be immediately available for posting, as it requires processing time. To handle this correctly, we need to check the job status.

If you're ready, let's get started! 🚀


Introduction

Uploading videos to Bluesky involves more than just sending a file—it requires authentication, handling the upload process, and ensuring the video is processed before posting. This guide walks you through each step, from authentication to publishing your video.

1. Authentication

Before uploading a video, we need to authenticate by either retrieving an existing session or creating a new one.

Using an Existing Session

If you have a saved session, you can load it like this:

use Atproto\Responses\Com\Atproto\Server\CreateSessionResponse;

$sessionJSON = \fetchSavedSessionFromDatabase(); // Replace with your function
$session = new CreateSessionResponse(json_decode($sessionJSON, true));

Creating a New Session

If there's no existing session, authenticate with your credentials:

use Atproto\Client;

$client = new Client();
$client->authenticate('handle', 'password'); // Replace with your credentials

Once authenticated, we’ll use this session to retrieve our PDS details and generate a new token in the next step.

2. Generating a Temporary Key

To upload a video to our PDS server, we need the necessary permissions, which means authenticating and obtaining an additional token.

First, we retrieve the PDS server address associated with our account. This will be required when requesting the token.

Since this information is already included in the session we created earlier, we can access it directly without making extra requests:

$session = $client->authenticated();
$pdsEndpoint = $session->didDoc()->service()
->firstWhere('id', '#atproto_pds')['serviceEndpoint'];

// Extracting only the host part from the PDS endpoint for our request
$pdsHost = str_replace(
["https://", "http://"],
"did:web:",
$pdsEndpoint
);

Now that we have identified our PDS host, we can set an expiration time for the temporary token.

use Carbon\Carbon;

// Setting expiration time to 15 minutes
$tokenExpiredTime = Carbon::now()->addMinutes(15)->timestamp;

Next, we define the purpose of the token request. This can be considered similar to a scope. Here, we use the NSID of the lexicon.

$scope = bskyFacade()->uploadBlob()->nsid();

With all the required data in place, we are now ready to send the request and obtain our token:

$token = bskyFacade()->getServiceAuth()
->lxm($scope)
->aud($pdsHost)
->exp($tokenExpiredTime)
->send()
->token();

Now that we have successfully obtained the temporary token, we can proceed to the next step and use it to upload our video. 🚀

3. Uploading the Video to PDS

First, we send the video file to the PDS server.

use Atproto\Support\FileSupport;

$filePath = 'path/to/video.mp4';
$file = new FileSupport($filePath);

$uploadedVideo = bskyFacade()->uploadVideo(basename($filePath), $file, $token)->send();

Handling the Uploaded Video

Before using the uploaded video in a post creation request (com.atproto.repo.createRecord lexicon), we need to ensure that it has been fully processed.

Since small videos might upload instantly depending on size, speed, and server conditions, it's good practice to first check if the response already contains a blob field instead of immediately entering a loop.

use Atproto\DataModel\Blob\Blob;

// Check if the video upload returned a blob
if ($videoBlob = $uploadedVideo->has('blob')) {
$videoBlob = Blob::viaArray($uploadedVideo->blob());
}

If the condition is not met, we need to keep checking until we receive the required blob value.

while (! $videoBlob) {
$jobStatusResponse = bskyFacade()
->getJobStatus($uploadedVideo->jobId()) // Use the job ID from the upload response
->send();

if ($jobStatusResponse->jobStatus()->state() === 'JOB_STATE_COMPLETED') {
$videoBlob = Blob::viaArray($jobStatusResponse->jobStatus()->blob());
}

// Wait for 0.5 seconds before checking again
sleep(0.5);
}

This ensures that our variable receives the required value. Now, let’s move on to the next step—creating and publishing a post with the uploaded video. 🚀

4. Creating a New Post

To use the video blob in our post, we first need to convert it into an embed.

use Atproto\Lexicons\App\Bsky\Embed\Video;

$videoEmbed = new Video($videoBlob);

Now, let's create our post.

$post = bskyFacade()->post()
->text("Hello, BlueSky! This is a video.")
->embed($videoEmbed);

To publish the post, we send the request.

$createdRecordRes = bskyFacade()->createRecord()
->repo($client->authenticated()->handle())
->collection($post->nsid())
->record($post)
->send();

Finally, we extract the post ID and generate the URI to access it.

$username = $session->handle();
$postNsid = $post->nsid();

$segments = explode("/", $createdRecordRes->uri());
$postId = end($segments);

$uri = "https://bsky.app/profile/$username/post/$postId";

echo $uri . PHP_EOL;

And here’s our result: https://bsky.app/profile/shahmal1yevv.bsky.social/post/3lk6krhhtyn2e 🚀

Conclusion

In this guide, we covered the complete process of uploading a video to Bluesky using the Bluesky SDK. We walked through:

  • Authenticating and retrieving a session,
  • Generating a temporary token for the PDS server,
  • Uploading the video and managing the job-based processing,
  • Creating and publishing a post with the uploaded video.

By following these steps, you can seamlessly integrate video uploads into your Bluesky applications. For the full example, check out the Video Upload Example in the documentation.

Now, it's time to experiment and enhance your content with videos! 🚀