Adding uploads to a SvelteKit app comes down to a single decision about how much of the upload layer you want to own. Filestack’s JavaScript SDK covers uploads, storage, a global CDN, and image processing, so a single page component and one server route get you a working, secured upload. This guide ships exactly that, and you can grab a free API key and build it in your own project as you read.
The browser sends the file straight to Filestack, and a small +server.js route signs the short-lived credentials that keep your app secret on the server. You write the upload once, point your components at it, and spend the rest of your time on the parts of your product that need you.
Every snippet runs against the real filestack-js SDK. Copy them in order and you will have a working upload, signed credentials, and a resized image by the end.
Key takeaways
- The SDK works in the browser with just your public API key, so a basic upload is one init call and one client.upload(file) call
- A SvelteKit +server.js route signs a short-lived policy with your app secret, which lives in $env/static/private and stays server-side
- The signing logic is a handful of node:crypto lines, so the server route needs no extra packages
- client.upload accepts the File object from an <input type=”file”> directly, so the bytes travel straight from the browser to Filestack
- Transformations are delivery-time CDN URLs, so resizing an image is a string you build from the returned handle
Before you start
You need:
- Node 18 or higher
- A SvelteKit project (npm create svelte@latest)
- A Filestack account for your API key and app secret
- Familiarity with Svelte components and SvelteKit routing
Pull your API key and app secret from the Filestack developer portal. The API key is fine in client code. The app secret stays on the server and signs every policy.
Step 1: Install and set environment variables
Install the SDK:
npm install filestack-js
Add your keys to .env:
PUBLIC_FILESTACK_API_KEY=Axxxxxxxxxxxxxxxxxxxxx
FILESTACK_APP_SECRET=your-app-secret-here
SvelteKit exposes anything prefixed with PUBLIC_ to the browser through $env/static/public. Everything else stays server-only in $env/static/private. That split is the whole security story in one naming convention.
Step 2: Get a working upload with the public key
Filestack apps start with security off, so an API key is enough for your first upload. Create src/routes/+page.svelte:
<script>
import { onMount } from 'svelte';
import { PUBLIC_FILESTACK_API_KEY } from '$env/static/public';
let client;
let result = $state(null);
let uploading = $state(false);
onMount(async () => {
const filestack = await import('filestack-js');
client = filestack.init(PUBLIC_FILESTACK_API_KEY);
});
async function handleFile(event) {
const file = event.currentTarget.files?.[0];
if (!file) return;
uploading = true;
result = await client.upload(file);
uploading = false;
}
</script>
<input type="file" onchange={handleFile} />
{#if uploading}<p>Uploading...</p>{/if}
{#if result}
<p>Uploaded {result.filename}</p>
<img src={result.url} alt={result.filename} width="320" />
{/if}
Run npm run dev, pick a file, and you have a working upload. The result object carries everything you need next: result.handle, result.url, result.filename, result.mimetype, and result.size. Save the handle in your database, since it is the durable identifier you reuse for signed reads, transformations, and deletes. The URL is convenience metadata.

Step 3: Sign a policy on the server
Once you turn on security in the developer portal, every upload needs a policy and a signature. Both come from a short-lived security policy signed with your app secret. Create src/routes/api/filestack-creds/+server.js:
import { json } from '@sveltejs/kit';
import { createHmac } from 'node:crypto';
import { FILESTACK_APP_SECRET } from '$env/static/private';
function signPolicy(policy, secret) {
const encoded = Buffer.from(JSON.stringify(policy))
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_');
const signature = createHmac('sha256', secret).update(encoded).digest('hex');
return { policy: encoded, signature };
}
export function GET() {
const policy = {
expiry: Math.floor(Date.now() / 1000) + 300, // valid for 5 minutes
call: ['pick', 'store', 'read', 'convert']
};
return json(signPolicy(policy, FILESTACK_APP_SECRET));
}
This route returns a five-minute credential scoped to the calls you allow. Keep it behind your own auth so only signed-in users can request one. The app secret never leaves the server, and the browser only ever sees the encoded policy and its signature.
Step 4: Upload with the signed credentials
Update the handler to fetch credentials first, then initialize the client with that security object:
async function handleFile(event) {
const file = event.currentTarget.files?.[0];
if (!file) return;
uploading = true;
const filestack = await import('filestack-js');
const security = await fetch('/api/filestack-creds').then((r) => r.json());
const secured = filestack.init(PUBLIC_FILESTACK_API_KEY, { security });
result = await secured.upload(file);
uploading = false;
}
The flow stays the same from the user’s side. The only change is that the client now carries a signed, expiring policy, so Filestack accepts the upload on a secured app.
Step 5: Resize on delivery
Filestack transformations happen at delivery time. Build a CDN URL from the returned handle:
{#if result}
<img
src={`https://cdn.filestackcontent.com/resize=width:600/${result.handle}`}
alt={result.filename}
/>
{/if}
The resize runs on the Filestack Processing Engine and the result is cached on the CDN, so your SvelteKit app never touches the bytes. The same URL pattern handles cropping, format conversion, and compression. With security on, append the read credentials as query parameters:
const src =
`https://cdn.filestackcontent.com/resize=width:600/${result.handle}` +
`?policy=${security.policy}&signature=${security.signature}`;
Putting it all together
One page component, one +server.js route, and the real SDK. The browser handles the transfer, the route signs short-lived policies with node:crypto, the app secret stays server-side, and a resize is a URL built from the handle. That covers the upload pipeline most apps need, and you wrote about forty lines to get it.
Create your free Filestack API key, wire up the page component and the policy route above, and you have secured uploads and transformations in your SvelteKit app the same afternoon. When you size it for production traffic, Filestack pricing scales with uploads, transformations, and bandwidth.
Carl is a Product Marketing Manager at Filestack with four years of hands-on experience in React, JavaScript, Django, and Python. He bridges the gap between product and developer, translating how Filestack’s APIs and SDKs actually work into content that’s useful for the engineers building with them. His writing covers file handling workflows, upload integrations, and real-world implementation patterns, written from the perspective of someone who has built with these tools firsthand.
Read More →