Add video upload and playback to SvelteKit
Use FastPix SDKs to add resumable uploads and adaptive-bitrate playback to a SvelteKit application.
Uploading video is different from uploading other files. Large uploads need to resume after interruptions, and media must be processed into streaming formats before they can be played reliably across devices.
FastPix handles uploads, processing, and adaptive streaming. Your application only needs to create a signed upload URL on the server and render a player. Uploads go directly from the browser to FastPix, so video data never passes through your SvelteKit server.
This guide uses the FastPix React Uploader inside a small Svelte wrapper because the uploader is currently available as a React component. The FastPix Player is a web component, so it works directly in Svelte without React. If you’d rather not add React at all, resumable uploads for web shows the underlying @fastpix/resumable-uploads engine, which is plain JavaScript: you build the UI in Svelte and drive the engine yourself.
How it works
The upload workflow is straightforward:
- Your SvelteKit server creates a signed upload URL using the FastPix Node SDK.
- The React Uploader uploads the selected file directly to FastPix using the signed URL.
- FastPix processes the uploaded media asynchronously.
- FastPix sends a
video.media.readywebhook when the media is ready for playback. - Your application retrieves the playback ID and plays the video using the FastPix Player.
What you’ll build
By the end of this guide, you’ll have:
- A SvelteKit application that uploads videos directly to FastPix.
- Resumable uploads with upload progress and retry support.
- A FastPix Player that streams the uploaded video.
- A workflow that waits for media processing before playback.
Before you begin
- A FastPix account with an Access Token ID and Secret Key. See get your Access Token ID and Secret Key.
- A SvelteKit application with server-side rendering enabled.
- Node.js.
Install
From your project directory:
The Node SDK talks to the FastPix API from your server and the Web Player plays the result. react and react-dom are peer dependencies of the React Uploader. They’re used only by the upload component, not by the rest of your app.
Add your credentials to .env:
Create the SDK client in a .server.ts file, reading them through $env/static/private. SvelteKit refuses to import that module into client code, so the credentials can’t reach the browser even by accident:
Create an upload URL
Signed upload URLs are created with your Secret Key, so this belongs on the server. A +server.ts file gives you an endpoint the browser can call:
src/routes/api/upload-url/+server.ts
src/routes/api/upload-url/+server.js
NOTE:
X-Client-Type: web-browsertells FastPix that a browser will perform the upload, so the signed URL is issued for browser use. Send it whenever the file is uploaded from a browser, including this setup, where your server requests the URL and the browser uploads to it.Leave it out when your own server, a CLI, or a native Android or iOS app uploads the bytes. See upload videos from device.
Keep the mediaId. It identifies the media everywhere else in the API, and you’ll use it later to play the video. Full options are in upload videos from device, or use create media from a URL to import video you already host.
Add the uploader
The uploader is a React component, so mount it into a <div> your Svelte component owns. onMount runs only in the browser, which also keeps the component out of server rendering:
src/lib/Uploader.svelte (TypeScript)
src/lib/Uploader.svelte (JavaScript)
Then use it like any other Svelte component:
That’s a working uploader: drag and drop, a file picker, progress, and pause, resume, and cancel controls.
You can select or drag a video into the uploader and the upload begins with visible progress.
createElement is used instead of JSX so the project needs no JSX tooling. onDestroy unmounts the React root. Without it, navigating away leaves the root attached and leaks.
Build your own layout instead
The sub-components read upload state through React context, which can’t cross into Svelte markup. Compose them inside the same root.render call so they stay in one React tree:
autoStart={false} holds the file until someone presses start, which is why a start button appears here and not in the default layout.
Passing Svelte elements as children of a React component doesn’t work. They render outside the provider and the sub-components fail to find their state. Everything that needs upload state has to be created with createElement.
Tracking progress yourself, driving the uploader from a ref, reading live state with useUploaderContext(), and building a fully headless UI are covered in the uploader README.
Respond to upload events
The uploader reports its lifecycle through callbacks. Pass them in the same props object you give createElement: onProgress, onSuccess, onError, and more are all optional:
onSuccess fires when the bytes finish uploading. The media still has to be encoded before it can play, which is what the next section covers. The full callback list is in the uploader README.
Wait for the media to be ready
A finished upload isn’t a playable video yet. It still has to be encoded. FastPix sends a video.media.ready webhook once playback is available.
Register your endpoint under Org Settings > Webhooks (see Set up webhooks), then handle the event in another +server.ts:
src/routes/api/fastpix/webhook/+server.ts
src/routes/api/fastpix/webhook/+server.js
WARNING:
Verify the signature before trusting a payload. Your endpoint is a public URL, so anyone who finds it could claim a video is ready. Check theFastPix-Signatureheader, an HMAC-SHA256 of the raw body, as described in Set up webhooks. Read the body withrequest.text()and verify that exact string. A parsed and re-serialized body produces different bytes and never matches.
Other events, including video.media.failed, are in the webhook event reference.
Webhooks can’t reach localhost, so while developing either expose your dev server through a tunnel or check the status directly:
Play the video
<fastpix-player> is a web component, so it needs no React and goes straight into Svelte markup. It registers itself against customElements, a browser-only API, so import it from onMount rather than at module scope.
Fetch the media in a server load. That runs on the server, so your credentials stay there and only the playback ID reaches the browser:
src/routes/play/[mediaId]/+page.server.ts
src/routes/play/[mediaId]/+page.server.js
src/routes/play/[mediaId]/+page.svelte (TypeScript)
src/routes/play/[mediaId]/+page.svelte (JavaScript)
Give it a size, since it fills whatever container it sits in:
A playback ID isn’t the same as a media ID. One media asset can carry several playback IDs with different access policies. This guide uses accessPolicy: "public", so the ID alone is enough to play the video. Private and DRM playback need a signed token, covered in play uploaded videos along with autoplay, captions, and the player’s full attribute and event surface.
Troubleshooting
Uploads fail with “error code 0” after 5 attempts, but the video still processes.
The signed URL was created without the X-Client-Type: web-browser header, so it isn’t valid for uploads started from a browser. Add it to your upload call as shown above. The rule is about who uploads the bytes, not who requests the URL. A browser doing the upload needs the header even though your server is the one asking for the URL.
Sub-components render but show no state, or throw about a missing provider.
They’re being created outside the React tree, usually by writing them as Svelte markup. Build every component that needs upload state with createElement inside the same root.render call.
document is not defined, or the uploader breaks the server render.
React DOM is being touched during SSR. Create the root inside onMount, which never runs on the server.
ReferenceError: window is not defined on the play page.
The player is being imported at module scope. Import it inside onMount, as above.
Cannot import $env/static/private into client-side code.
A component that runs in the browser is importing your server module. Only +server.ts, +page.server.ts, and other server files may import fastpix.server.ts.
Chunk size is rejected.
chunkSize is in KB, between 5120 and 512000, in multiples of 256.