upload_max_filesize, post_max_size, max_execution_time, memory_limit. Every Laravel video upload thread works through the same four settings, restarts PHP-FPM, and then discovers the host enforces its own cap regardless. The settings aren't wrong. They answer a question you don't have to ask. There's a version of this feature where the video file never enters a PHP process at all.
TL;DR
upload_max_filesize and post_max_size only limit files that enter a PHP request. In a direct upload they never do. Your Laravel controller calls directUploadVideoMedia() and returns a signed URL. The browser uploads to that URL instead. The request Laravel handles is a few hundred bytes, whether the video is 50 MB or 5 GB. Three Laravel-specific things will bite. Use composer require fastpix/sdk --with-all-dependencies for the Guzzle conflict. Read credentials with config('fastpix.username') so config:cache doesn't break them. And send the X-XSRF-TOKEN header so the route stops returning 419.
Why upload_max_filesize does not apply to Laravel video upload
upload_max_filesize governs what PHP accepts into a request, and it has nothing to say about a file the browser sends somewhere else.
In a direct upload, Laravel's only job is to mint a signed URL. The browser then uploads to that URL. Your VideoController handles a request with no body worth speaking of. It returns some JSON and is done in milliseconds, while a 4 GB file transfers in the background.
| What you are tuning | What it actually limits | Applies to direct upload? |
|---|---|---|
| upload_max_filesize | Size of a file inside a PHP request | No |
| post_max_size | Total POST body PHP will parse | No |
| max_execution_time | How long the PHP script runs | No, the script returns immediately |
| memory_limit | Memory the PHP process holds | No, nothing is buffered |
| Nginx client_max_body_size | Size the web server accepts | No, the bytes never hit your server |
What you need before you start
- A Laravel application on PHP 8.x with Composer and Node.js.
- A FastPix account with an Access Token ID and Secret Key from activate your account.
- Laravel's React starter kit, or Inertia with React added, since the uploader is a React component. The player needs no React and works in Blade.
Install the Laravel video packages, and the Composer conflict
Three packages cover the whole flow. One of them fights your lockfile:
composer require fastpix/sdk
npm install @fastpix/fp-react-uploader @fastpix/fp-playerIf composer require fastpix/sdk fails on a version conflict, the cause is almost always Guzzle. Your project has a version locked that the SDK's constraint disagrees with, and Composer refuses to touch locked transitive dependencies on your behalf. Tell it to:
composer require fastpix/sdk --with-all-dependenciesRead Laravel credentials through config, not env
Laravel has one trap here that looks like a credentials bug and isn't. Put the values in .env as usual:
FASTPIX_USERNAME=your-access-token-id
FASTPIX_PASSWORD=your-secret-keyThen read them through a config file rather than calling env() from application code:
// config/fastpix.php
return [
'username' => env('FASTPIX_USERNAME'),
'password' => env('FASTPIX_PASSWORD'),
];The reason is php artisan config:cache. Once config is cached, env() stops reading .env anywhere outside config files. Credentials that worked all through development return null on the first cached deploy. Reading config('fastpix.username') keeps working because the value was baked in when the cache was built.
Wrap the FastPix PHP SDK client once
The SDK client needs the header that tells FastPix a browser will push the bytes. Set it on the Guzzle client and it applies to every call. One fewer thing to forget:
// app/Services/FastPixService.php
namespace App\Services;
use FastPix\Sdk\Fastpixsdk;
use FastPix\Sdk\Models\Components\Security;
use GuzzleHttp\Client;
class FastPixService
{
public static function make(): Fastpixsdk
{
return Fastpixsdk::builder()
->setSecurity(new Security(
username: config('fastpix.username'),
password: config('fastpix.password'),
))
->setClient(new Client([
'headers' => ['X-Client-Type' => 'web-browser'],
]))
->build();
}
}X-Client-Type: web-browser describes who uploads the file, not who requests the URL. Your server is always the requester. Omit it and uploads fail with error code 0 after five attempts, while the media still processes. Confusing, because something evidently worked.
The Laravel controller that creates the upload URL
Signed upload URLs are created with your Secret Key, so this belongs on the server and nowhere else:
<?php
// app/Http/Controllers/VideoController.php
namespace App\Http\Controllers;
use App\Services\FastPixService;
use FastPix\Sdk\Models\Operations\DirectUploadVideoMediaAccessPolicy;
use FastPix\Sdk\Models\Operations\DirectUploadVideoMediaRequest;
use FastPix\Sdk\Models\Operations\PushMediaSettings;
class VideoController extends Controller
{
public function createUpload()
{
$upload = FastPixService::make()->inputVideo->directUploadVideoMedia(
new DirectUploadVideoMediaRequest(
corsOrigin: '*',
pushMediaSettings: new PushMediaSettings(
accessPolicy: DirectUploadVideoMediaAccessPolicy::Public,
),
),
);
return response()->json([
'url' => $upload->object->data->url,
'mediaId' => $upload->object->data->uploadId,
]);
}
}Removing the file from the request also removes the queued encoding job most Laravel apps run, which is the argument in streaming video from Laravel without FFmpeg.
Keep the mediaId. Store it against your own record before the upload starts, because it's how you match the video to the row later. The URL is the only half the browser needs. Every option on that call is documented in upload a video from a device.
You can prove this end to end before committing to it. The free plan covers 10 videos and 100K streaming minutes a month with no credit card.
// routes/web.php
use App\Http\Controllers\VideoController;
Route::post('/api/upload-url', [VideoController::class, 'createUpload']);Your first Laravel upload request will return 419
Routes in routes/web.php are CSRF-protected, and a fetch call doesn't send Laravel's token on its own. The token is sitting in the XSRF-TOKEN cookie. Laravel expects it back as the X-XSRF-TOKEN header:
// resources/js/pages/upload.tsx
import { FastPixUploader } from '@fastpix/fp-react-uploader';
import '@fastpix/fp-react-uploader/styles.css';
function csrfToken(): string {
const cookie = document.cookie.split('; ').find((c) => c.startsWith('XSRF-TOKEN='));
return cookie ? decodeURIComponent(cookie.split('=')[1]) : '';
}
async function createUpload() {
const response = await fetch('/api/upload-url', {
method: 'POST',
headers: { 'X-XSRF-TOKEN': csrfToken() },
});
const { url } = await response.json();
return url as string;
}
export default function Upload() {
return <FastPixUploader endpoint={createUpload} accept="video/*" />;
}endpoint takes a function rather than a URL string on purpose. The function runs when a file is picked, so a URL is minted per upload instead of per page load. A page that renders a hundred times without an upload mints nothing.
That single component gives you drag and drop, a file picker, progress, and working pause, resume and cancel controls. The chunking underneath is what makes a dropped connection survivable. The general theory is in how to upload large video files efficiently using chunking.
Knowing when the uploaded video is ready to play
onSuccess fires when the bytes are delivered. The media still has to be encoded before anything can play it, and that happens asynchronously.
FastPix announces it with a video.media.ready webhook, which arrives at a route you register. That route has its own 419 problem, and it's the mirror image of the first. Why your Laravel video route returns a 419 covers both. FastPix can't send a CSRF token, so the route has to be excluded rather than fixed.
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'api/fastpix/webhook',
]);
})Excluding it means anything on the internet can post to that URL, so signature verification isn't optional. Verify the FastPix-Signature header against the raw bytes with $request->getContent(), because a decoded and re-encoded payload produces different bytes and never matches. The verification detail is covered in the video.media.ready webhook, and how to verify it.
While developing, webhooks can't reach localhost. Either tunnel, or poll the media directly:
$media = FastPixService::make()->manageVideos->getMedia($mediaId);
// $media->object->data->status?->value === 'Ready'
// status is a backed enum in SDK 1.1.1, so compare its value, not the objectLaravel video playback needs no React at all
The uploader is a React component. The player isn't, and this is worth stating plainly because it decides whether a Blade app has to adopt Inertia.
<fastpix-player> is a web component, so it works in a Blade view directly:
<fastpix-player playback-id="{{ $playbackId }}" stream-type="on-demand"></fastpix-player>with import '@fastpix/fp-player'; in resources/js/app.js. Only the upload UI requires React, and a Blade app that only needs playback can skip Inertia entirely.
In an Inertia app with SSR enabled, import the player inside useEffect instead. Player 1.0.21 and later guard their customElements registration, so this is no longer about a crash: a static import simply puts the whole player bundle into the payload for every page that might show a video.
Add video upload to your Laravel app
Install fastpix/sdk, add one controller action that returns $upload->object->data->url, and point <FastPixUploader> at it with the X-XSRF-TOKEN header. Your php.ini stays exactly as it is. The free plan covers 10 videos and 100K streaming minutes a month, no credit card. Push a file large enough to have broken the old route.
Frequently Asked Questions (FAQs)
Do I need to increase upload_max_filesize for video in Laravel?
Not if the file never reaches PHP. upload_max_filesize limits what PHP accepts inside a request. In a direct upload, the browser sends the file to a signed storage URL instead. Laravel only returns JSON containing that URL, so the request it handles is tiny at any video size.
Why does my Laravel upload route return 419?
Because routes in routes/web.php are CSRF-protected and your fetch call is not sending the token. Read it from the XSRF-TOKEN cookie and send it back as the X-XSRF-TOKEN header. A webhook route gets a 419 for a different reason. The sender cannot produce a token at all. Exclude that route in bootstrap/app.php and verify the signature instead.
Can I upload video from Laravel without Inertia or React?
Playback yes, uploading not with the prebuilt component. <fastpix-player> is a web component and drops straight into Blade. The uploader ships as a React component. A Blade-only app either adds Inertia for that one screen, or drives the underlying resumable-upload engine from its own JavaScript.
Why do my FastPix credentials stop working after deploying?
Almost certainly php artisan config:cache. Once config is cached, env() returns null outside config files, so any credential read with env() in application code disappears. Put the values in config/fastpix.php and read them with config('fastpix.username').
Does the video file pass through my Laravel server?
No. Your server creates the signed URL and nothing else. The browser uploads directly to that URL. Your PHP workers, your memory limit, and your web server's body-size cap are all uninvolved. A long upload never occupies a PHP-FPM process.






