General

An Introduction to Laravel Queues and Temporary URLs

Jamie Kuppens
9 min read
Jamie - An Introduction to Laravel Queues and Temporary URLs
This article was written over 18 months ago and may contain information that is out of date. Some content may still be relevant, but please refer to official documentation for the latest information.

Laravel is a mature, robust, and powerful web framework that makes developing PHP applications a breeze. In particular, I want to demonstrate how to create a website that can be used to convert videos online using queue jobs for processing and temporary URLs for downloading the converted files.

This article is aimed at those who aren’t very familiar with Laravel yet.

Prerequisites

There are many ways to set up Laravel, and which is the best method may depend on your operating system or preference. I have found Laravel Herd to be very easy to use if you’re using Windows or macOS. Herd is a Laravel development environment that has everything you need with minimal configuration required. Command-line tools are installed and added to your path, and background services are configured automatically.

If you’re developing on Linux then Herd is not an option. However, Laravel Sail works for all major operating systems and uses a Docker based environment instead. You can find a full list of supported installation methods in the Laravel documentation. To keep things simpl,e this article assumes the use of Herd, though this won’t make a difference when it comes to implementation.

You will also need a text editor or IDE that has good PHP support. PhpStorm is a great editor that works great with Laravel, but you can also use VSCode with the Phpactor language server, and I’ve found Phpactor to work quite well.

Project Setup

With a development environment setup, you can create a new Laravel project using composer, which is the most popular package manager for PHP. Herd installs composer for you. composer installs dependencies and lets you run scripts. Let’s create a Laravel project using it:

composer create-project laravel/laravel laravel-video-converter

Once that is done you can navigate into the project directory and start the server with artisan:

php artisan serve

Awesome! You can now navigate to http://localhost:8000/ and see the Laravel starter application’s welcome page. Artisan is the command-line interface for Laravel. It comes with other utilities as well such as a database migration tool, scripts for generating classes, and other useful things.

Uploading Videos Using Livewire

Livewire is a library that allows you to add dynamic functionality to your Laravel application without having to add a frontend framework. For this guide we’ll be using Livewire to upload files to our server and update the status of the video conversion without requiring any page reloads.

Livewire can be installed with composer like so.

composer require livewire/livewire

With it installed we need to make a Livewire component now. This component will act as the controller of our video upload page.

php artisan make:livewire video-uploader

With that done you should see two new files were created according to the output of the command, one being a PHP file and the other being a Blade file. Laravel has its own HTML template syntax for views that allow you to make your pages render dynamically.

For this demo we’ll make the video conversion page render at the root of the site. You can do this by going to routes/web.php and editing the root route definition to point to our new component.

<?php

use App\Livewire\VideoUploader;
use Illuminate\Support\Facades\Route;

Route::get('/', VideoUploader::class);

However, if we visit our website now it will return an error. This is due to the app template being missing, which is the view that encapsulates all page components and contains elements such as the document head, header, footer, etc.

Create a file at resources/views/components/layouts/app.blade.php and put the following contents inside. This will give you a basic layout that we can render our page component inside of.

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
	<head>
    	<meta charset="utf-8">
    	<meta name="viewport" content="width=device-width, initial-scale=1">
    	<title>WebM Video Converter</title>

    	<style>
        	html {
            	font-family: sans-serif;
            	background-color: #eaeaea;
        	}

        	main {
            	max-width: 1000px;
            	margin: 100px auto 0 auto;
            	padding: 32px;
            	border-radius: 24px;
            	background-color: white;
        	}

        	h1 {
            	margin-top: 0;
        	}

        	a {
            	text-decoration: none;
        	}
    	</style>
	</head>
	<body>
    	<main>
        	{{ $slot }}

        	<footer>
            	Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }})
        	</footer>
    	</main>
	</body>
</html>

The {{ $slot }} string in the main tag is a Blade echo statement. That is where our Livewire component will be injected when loading it.

Now, let’s edit the Livewire component’s template so it has something meaningful in it that will allow us to verify that it renders correctly. Edit resources/views/livewire/video-uploader.blade.php and put in the following:

<div>
	<h1>Hello, Laravel!</h1>
</div>

With that done you can go to the root of the site and see this hello message rendered inside of a box. Seeing that means everything is working as it should. We may as well delete the welcome template since we’re not using it anymore. This file is located at resources/views/welcome.blade.php.

Now, let’s go ahead and add uploading functionality. For now we’ll just upload the file into storage and do nothing with it. Go ahead and edit app/Livewire/VideoUploader.php with the following:

<?php

namespace App\Livewire;

use Illuminate\Contracts\View\View;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use Livewire\WithFileUploads;

class VideoUploader extends Component
{
	use WithFileUploads;

	/**
 	* @var TemporaryUploadedFile
 	*/
	#[Validate('mimetypes:video/avi,video/mpeg,video/quicktime')]
	public $video;

	public function save(): void
	{
    	$videoFilename = $this->video->store();
	}

	public function render(): View
	{
    	return view('livewire.video-uploader');
	}
}

This will only allow uploading files with video file MIME types. The $video class variable can be wired inside of the component’s blade template using a form.

Create a form in resources/views/livewire/video-uploader.blade.php like so:

<div>
	<h1>WebM Video Converter</h1>

	<form wire:submit="save">
    	<input type="file" wire:model="video">

    	@error('video') <span class="error">{{ $message }}</span> @enderror

    	<button type="submit">Convert Video</button>
	</form>
</div>

You will note a wire:submit attribute attached to the form. This will prevent the form submission from reloading the page and will result in Livewire calling the component’s save method using the video as a parameter. The $video property is wired with wire:model="video".

Now you can upload videos, and they will be stored into persistent storage in the storage/app/private directory. Awesome!

Increase the Filesize Limit

If you tried to upload a larger video you may have gotten an error. This is because the default upload size limit enforced by Livewire and PHP is very small. We can adjust these to accommodate our use-case.

Let’s start with adjusting the Livewire limit. To do that, we need to generate a configuration file for Livewire.

php artisan livewire:publish --config

All values in the generated file are the defaults we have been using already. Now edit config/livewire.php and make sure the temporary_file_upload looks like this:

...

'temporary_file_upload' => [
	'disk' => null,    	// Example: 'local', 's3'          	| Default: 'default'
	'rules' => null,   	// Example: ['file', 'mimes:png,jpg']  | Default: ['required', 'file', 'max:12288'] (12MB)
	'directory' => null,   // Example: 'tmp'                  	| Default: 'livewire-tmp'
	'middleware' => null,  // Example: 'throttle:5,1'         	| Default: 'throttle:60,1'
	'preview_mimes' => [   // Supported file types for temporary pre-signed file URLs...
    	'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
    	'mov', 'avi', 'wmv', 'mp3', 'm4a',
    	'jpg', 'jpeg', 'mpga', 'webp', 'wma',
	],
	'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated...
	'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs...
	'rules' => 'max:102400', // NEW: Override default so we can upload long videos.
],

...

The rules key allows us to change the maximum file size, which in this case is 100 megabytes.

This alone isn’t good enough though as the PHP runtime also has a limit of its own. We can configure this by editing the php.ini file. Since this article assumes the use of Herd, I will show how that is done with it.

Go to Herd > Settings > PHP > Max File Upload Size > and set it to 100. Once done you need to stop all Herd services in order for the changes to take effect. Also make sure to close any background PHP processes with task manager in-case any are lingering, as this happened with me. Once you’ve confirmed everything is shut off, turn on all the services again.

If you’re not using Herd, you can add the following keys to your php.ini file to get the same effect:

upload_max_filesize=100M
post_max_size=100M

Creating a Background Job

Now, let’s get to the more interesting part that is creating a background job to run on an asynchronous queue. First off, we need a library that will allow us to convert videos. We’ll be using php-ffmpeg. It should be noted that FFmpeg needs to be installed and accessible in the system path. There are instructions on their website that tell you how to install it for all major platforms. On macOS this is automatic if you install it with homebrew. On Windows you can use winget.

On macOS and Linux you can confirm that ffmpeg is in your path like so:

which ffmpeg

If a file path to ffmpeg is returned then it’s installed correctly. Now with FFmpeg installed you can install the PHP library adapter with composer like so:

composer require php-ffmpeg/php-ffmpeg

Now that we have everything we need to convert videos, let’s make a job class that will use it:

php artisan make:job ProcessVideo

Edit app/Jobs/ProcessVideo.php and add the following:

<?php

namespace App\Jobs;

use FFMpeg\FFMpeg;
use FFMpeg\Format\Video\WebM;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\File;

class ProcessVideo implements ShouldQueue
{
	use Queueable;

	private string $videoPath;

	private string $outputPath;

	public function __construct(string $videoPath, string $outputPath)
	{
    	$this->videoPath = $videoPath;
    	$this->outputPath = $outputPath;
	}

	public function handle(): void
	{
    	$tempOutputPath = "{$this->outputPath}.tmp";

    	// Convert the video to the WebM container format (SLOW).
    	$video = FFMpeg::create()->open($this->videoPath);
    	$video->save(new WebM, $tempOutputPath);
    	File::move($tempOutputPath, $this->outputPath);
	}
}

To create a job we need to make a class that implements the ShouldQueue interface and uses the Queueable trait. The handle method is called when the job is executed. Converting videos with php-ffmpeg is done by passing in an input video path and calling the save method on the returned object. In this case we’re going to convert videos to the WebM container format. Additional options can be specified here as well, but for this example we’ll keep things simple.

One important thing to note with this implementation is that the converted video is moved to a file path known by the livewire component. Later and to keep things simple we’re going to modify the component to check this file path until the file appears, and while for demo purposes this is fine, in an app deployed at a larger scale with multiple instances it is not. In that scenario it would be better to write to a cache like Redis instead with a URL to the file (if uploaded to something like S3) that can be checked instead.

Now let’s use this job! Edit app/Livewire/VideoUploader.php and let’s add some new properties and expand on our save method.

use App\Jobs\ProcessVideo;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Storage;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use Livewire\WithFileUploads;

class VideoUploader extends Component
{
	use WithFileUploads;

    	/**
     	* @var TemporaryUploadedFile
     	*/
    	#[Validate('mimetypes:video/avi,video/mpeg,video/quicktime')]
    	public $video;

    	public ?string $jobStatus = 'Inactive';

    	public ?string $outputVideoLink = null;

    	public ?string $outputPath = null;

    	public ?string $outputFilename = null;

    	public function save(): void
    	{
        	$this->jobStatus = 'In Progress';
        	$this->outputVideoLink = null;
        	$this->outputPath = null;
        	$this->outputFilename = null;

        	// Store the uploaded file and generate the input and output paths of
        	// the video to be converted for the job.
        	$videoFilename = $this->video->store();
        	$videoPath = Storage::disk('local')->path($videoFilename);
        	$videoPathInfo = pathinfo($videoPath);
        	$this->outputFilename = "{$videoPathInfo['filename']}.webm";
        	$this->outputPath = "{$videoPathInfo['dirname']}/{$this->outputFilename}";

        	// Add the long-running job onto the queue to be processed when possible.
        	ProcessVideo::dispatch($videoPath, $this->outputPath);
    	}

    	...
}

How this works is we tell the job where it can find the video and tell it where it should output the converted video when it’s done. We have to make the output filename be the same as the original with just the extension changed, so we use pathinfo to extract that for us.

The ProcessVideo::dispatch method is fire and forget. We aren’t given a handle of any kind to be able to check the status of a job out of the box. For this example we’ll be waiting for the video to appear at the output location.

To process jobs on the queue you need to start a queue worker as jobs are not processed in the same process as the server that we are currently running. You can start the queue with artisan:

php artisan queue:work

Now the queue is running and ready to process jobs! Technically you can upload videos for conversion right now and have them be processed by the job, but you won’t be able to download the file in the browser yet.

Generating a Temporary URL and Sending it with Livewire

To download the file we need to generate a temporary URL. Traditionally this feature has only been available for S3, but as of Laravel v11.24.0 this is also usable with the local filesystem, which is really useful for development.

Let’s add a place to render the download link and the status of the job. Edit resources/views/livewire/video-uploader.blade.php and add a new section under the form:

<div>
	<h1>WebM Video Converter</h1>

	<form wire:submit="save">
    	<input type="file" wire:model="video">

    	@error('video') <span class="error">{{ $message }}</span> @enderror

    	<button type="submit">Convert Video</button>
	</form>

	<div wire:poll>
    	<p>Job Status: {{ $jobStatus }}</p>
    	@if ($outputVideoLink != null)
        	<a href="{{ $outputVideoLink }}" download>Download Converted File</a>
    	@endif
	</div>
</div>

Note the wire:poll attribute. This will cause the Blade echo statements inside of the div to refresh occasionally and will re-render if any of them changed. By default, it will re-render every 2.5 seconds. Let’s edit app/Livewire/VideoUploader.php to check the status of the conversion, and generate a download URL.

use App\Jobs\ProcessVideo;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use Livewire\WithFileUploads;

class VideoUploader extends Component
{
	use WithFileUploads;

    	...

    	public function render(): View
    	{
        	if ($this->outputPath != null && File::exists($this->outputPath)) {
            	// Create a temporary URL that lasts for 10 minutes and allows the
            	// user to download the processed video file.
            	$this->outputVideoLink = Storage::temporaryUrl(
                	$this->outputFilename, now()->addMinutes(10)
            	);

            	$this->jobStatus = 'Done';
            	$this->outputPath = null;
            	$this->outputFilename = null;
        	}

        	return view('livewire.video-uploader', [
            	'jobStatus' => $this->jobStatus,
            	'outputVideoLink' => $this->outputVideoLink,
        	]);
    	}
}

Every time the page polls we check if the video has appeared at the output path. Once it’s there we generate the link, store it to state, and pass it to the view. Temporary URLs are customizable as well. You can change the expiration time to any duration you want, and if you’re using S3, you can also pass S3 request parameters using the optional 3rd argument.

Now you should be able to upload videos and download them with a link when they’re done processing!

Limitations

Although this setup works fine in a development environment with a small application, there are some changes you might need to make if you plan on scaling beyond that.

If your application is being served by multiple nodes then you will need to use a remote storage driver such as the S3 driver, which works with any S3 compatible file storage service. The same Laravel API calls are used regardless of the driver you use. You would only have to update the driver passed into the Storage facade methods from local to s3, or whichever driver you choose.

You also wouldn’t be able to rely on the same local filesystem being shared between your job workers and your app server either and would have to use a storage driver or database to pass files between them. This demo uses the database driver for simplicity’s sake, but it's also worth noting that by default, queues and jobs use the database driver, but SQS, Redis, and Beanstalkd can also be used. Consider using these other drives instead of depending on how much traffic you need to process.

Conclusion

In this article, we explored how to utilize queues and temporary URLs to implement a video conversion site. Laravel queues allow for efficient processing of long-running tasks like video conversion in a way that won’t bog down your backend servers that are processing web requests.

While this setup works fine for development, some changes would need to be made for scaling this such as using remote storage drivers for passing data between the web server and queue workers. By effectively leveraging Laravel’s features, developers can create robust and scalable applications with relative ease.

About the author

Jamie Kuppens

Jamie Kuppens

Senior Software Engineer

I’m a software engineer with an interest in web development and some more esoteric things like emulator development.

Keep reading

View all posts →

This Dot AI Field Notes - Anatomy of a Coding Harness

A coding agent is not magic, it’s a loop. We call this a harness. The harness is a deterministic layer of code that wraps an LLM....

1 min
AI

AI Is Speeding Up Development. But Where Are the New Bottlenecks?

AI is accelerating development, but it’s also exposing everything else that’s broken. At the Leadership Exchange, leaders unpacked how AI is reshaping the SDLC and what organizations need to address beyond just coding to make adoption successful. Moderated by Rob Ocel, VP of Innovation at This Dot Labs, the panel featured Itai Gerchikov at Anthropic and Harald Kirschner, Principal Product Manager for GitHub Copilot & VS Code at Microsoft. Panelists explored the current state of AI adoption across the software development lifecycle and shared practical insights into how organizations can effectively integrate AI tools. Panelists discussed how companies are investing in AI tools, skills, and managed competency programs to support developers. While AI can dramatically accelerate coding, the panel emphasized that adoption affects every stage of the SDLC. Bottlenecks now appear in testing, DevOps, product delivery, and marketing as AI speeds up development. Organizations that address technical debt and process inefficiencies are better positioned to extract maximum value from AI tools. The conversation also focused on opportunities and risks. Security, governance, and workforce education were highlighted as critical factors for adoption. Panelists stressed that AI initiatives should be aligned with broader business goals rather than pursued in isolation. They noted that companies experimenting at the cutting edge need to consider organizational readiness just as carefully as technical capabilities. Panelists also explored how leading organizations are navigating the early stages of adoption. Those ahead of the curve are using structured experimentation, prioritizing process improvements, and continuously evaluating outcomes to refine their AI strategies. Learning from these early adopters allows other organizations to anticipate emerging trends and prepare for the next phase of AI adoption rather than simply replicating past approaches. Key Takeaways Investing in AI skills and tools should be done thoughtfully, with clear alignment to business objectives. Examining the full SDLC helps identify bottlenecks that AI may accelerate or expose. Organizations can gain a competitive advantage by learning from early adopters and planning for where AI adoption is heading. AI adoption is not just a technical initiative; it is a strategic transformation that requires attention to people, process, and technology. Organizations that balance innovation with operational discipline will be best positioned to capture the full potential of AI across the software lifecycle. Seeing similar challenges in your own SDLC? Let’s compare notes. Join us at an upcoming Leadership Exchange or reach out to continue the conversation. Tracy can be reached at tlee@thisdot.co....

Calypso Hernandez2 mins
AI AdoptionAILeadership ExchangeEngineering Leadership

Making AI Deliver: From Pilots to Measurable Business Impact

A lot of organizations have experimented with AI, but far fewer are seeing real business results. At the Leadership Exchange, this panel focused on what it actually takes to move beyond experimentation and turn AI into measurable ROI. Over the past few years, many organizations have experimented with AI, but the challenge today is translating experimentation into measurable business value. Moderated by Tracy Lee, CEO at This Dot Labs, panelists featured Dorren Schmitt, Vice President IT Strategy & Innovation at Allen Media Group, Greg Geodakyan, CTO at Client Command, and Elliott Fouts, CAIO & CTO at This Dot Labs. Panelists discussed how companies are moving from early AI experiments to initiatives that deliver real results. They began by examining how experimentation has evolved over the past year. While many organizations did not fully utilize AI experimentation budgets in 2025, 2026 is showing a shift toward more intentional investment. Structured budgets and clearly defined frameworks are enabling companies to explore AI strategically and identify initiatives with high potential impact. The conversation then turned to alignment and ROI. Panelists highlighted the importance of connecting AI projects to corporate strategy and leadership priorities. Ensuring that AI initiatives translate into operational efficiency, productivity gains, and measurable business impact is essential. Companies that successfully align AI efforts with organizational goals are better equipped to demonstrate tangible outcomes from their investments. Moving from pilots and proofs of concept to production was another major focus. Governance, prioritization, and workflow integration were cited as essential for scaling AI initiatives. One panelist shared that out of nine proofs of concept, eight successfully launched, resulting in improvements in quality and operational efficiency. Panelists also explored the future of AI within organizations, including the potential for agentic workflows and reduced human in the loop processes. New capabilities are emerging that extend beyond coding tasks, reshaping how teams collaborate and how work is structured across departments. Key Takeaways Structured experimentation and defined budgets allow organizations to explore AI strategically and safely. Alignment with business priorities is essential for translating AI capabilities into measurable outcomes. Governance and workflow integration are critical to moving AI initiatives from pilot stages to production deployment. Successfully leveraging AI requires a balance between experimentation, strategic alignment, and operational discipline. Organizations that approach AI as a structured, measurable initiative can capture meaningful results and unlock new opportunities for innovation. Curious how your organization can move from AI experimentation to real impact? Let’s talk. Reach out to continue the conversation or join us at an upcoming Leadership Exchange. Tracy can be reached at tlee@thisdot.co....

Calypso Hernandez2 mins
AI AdoptionAILeadership ExchangeEngineering Leadership

What does it actually look like to build software with AI today? Not in theory, but in practice.

What does it actually look like to build software with AI today? Not in theory, but in practice. At the Leadership Exchange, this was the question at the center of the Developer Panel, where leaders from across the industry unpacked what’s really changing inside engineering teams and what organizations need to do right now to keep up. The Developer Panel at the Leadership Exchange explored the cutting edge of AI in software engineering and examined what organizations should focus on today to prepare for the future. Moderated by Jeff Cross, Co Founder & CEO at Nx, the panel featured Victor Savkin, Cofounder & CTO at Nx, Alex Sover, Vice President of Engineering at OpenAP, Brent Zucker, Senior Director of Engineering at Visa, and Jonathan Fontanez, AI Engineering Lead at This Dot Labs. Panelists shared insights into how AI is transforming the software development lifecycle and how teams can adopt tools effectively while preparing for organizational change. Panelists discussed emerging workflows, including CI in the loop, agentic healing, and context engineering. They examined how validation, code reviews, and PRDs are evolving alongside AI capabilities and how teams are integrating external sources such as production traces to improve quality and reliability. The discussion also covered what the next generation of agentic tools might look like and how these capabilities will shape engineering practices in the near future. Adoption of AI comes with challenges. Teams often rely on plugins or extensions without foundational understanding, and individual contributors may fear displacement. Panelists emphasized that education, governance, and skill building are essential for teams to manage AI agents effectively while maintaining quality. They also highlighted the need to standardize workflows and ensure organizational alignment to fully leverage AI capabilities. The conversation extended beyond technical challenges to organizational implications. Panelists discussed how teams can avoid issues like Conway’s Law, manage distributed teams effectively, and evolve engineering practices alongside AI adoption. Leadership and management strategies play a crucial role in ensuring that AI integration delivers meaningful outcomes while maintaining efficiency and alignment with business objectives. Key Takeaways AI workflows require both technical and organizational preparation. Education, governance, and skill development are essential for successful implementation. Forward looking teams are rethinking validation, CI pipelines, and context management to fully leverage agentic AI. The discussion highlighted that adopting AI at the cutting edge is not just about new tools it is about rethinking processes, workflows, and organizational culture. Companies that embrace this holistic approach are most likely to succeed in leveraging AI to its full potential. Are you interested in more conversations like this? Message us for an invite to the next, or for a private discussion around these topics. Tracy can be reached at tlee@thisdot.co....

Calypso Hernandez2 mins
AI AdoptionAILeadership ExchangeEngineering Leadership