You picked Solid for fine-grained reactivity and a small runtime. File uploads should not pull you back into managing storage buckets and a CDN. Filestack’s JavaScript SDK handles that layer, so a single component and one API route give you a working, secured upload that stays in app code. Create a free API key and build it as you read.
On the client, a signal pair tracks the upload state. On the server, a file in src/routes/api signs the short-lived credentials that keep your app secret off the browser. That is the whole boundary, and once it is in place your components talk to a plain SDK client.
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 SolidStart API route in src/routes/api signs a short-lived policy with your app secret, read from process.env so it stays server-side
- The signing logic is a handful of node:crypto lines, so the API 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
- A createSignal pair tracks upload progress, and the returned handle drives any delivery-time transformation
Before you start
You need:
- Node 18 or higher
- A SolidStart project (npm init solid@latest)
- A Filestack account for your API key and app secret
- Familiarity with Solid signals and SolidStart 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:
VITE_FILESTACK_API_KEY=Axxxxxxxxxxxxxxxxxxxxx
FILESTACK_APP_SECRET=your-app-secret-here
SolidStart runs on Vite, so anything prefixed with VITE_ reaches the browser through import.meta.env. The app secret has no prefix, so it stays server-only and you read it with process.env inside the API route.
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. Edit src/routes/index.tsx:
import { createSignal, onMount, Show } from 'solid-js';
import type { Client } from 'filestack-js';
export default function Home() {
let client: Client;
const [result, setResult] = createSignal<any>(null);
const [uploading, setUploading] = createSignal(false);
onMount(async () => {
const filestack = await import('filestack-js');
client = filestack.init(import.meta.env.VITE_FILESTACK_API_KEY);
});
async function handleFile(e: Event) {
const file = (e.currentTarget as HTMLInputElement).files?.[0];
if (!file) return;
setUploading(true);
setResult(await client.upload(file));
setUploading(false);
}
return (
<main>
<input type="file" onChange={handleFile} />
<Show when={uploading()}>
<p>Uploading...</p>
</Show>
<Show when={result()}>
<p>Uploaded {result().filename}</p>
<img src={result().url} alt={result().filename} width="320" />
</Show>
</main>
);
}
Run npm run dev, pick a file, and you have a working upload. The resolved object carries 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.ts:
import { json } from '@solidjs/router';
import type { APIEvent } from '@solidjs/start/server';
import { createHmac } from 'node:crypto';
function signPolicy(policy: object, secret: string) {
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(event: APIEvent) {
const policy = {
expiry: Math.floor(Date.now() / 1000) + 300, // valid for 5 minutes
call: ['pick', 'store', 'read', 'convert']
};
return json(signPolicy(policy, process.env.FILESTACK_APP_SECRET as string));
}
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, and follow the security best practices for scoping and expiry. The app secret stays in process.env on 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(e: Event) {
const file = (e.currentTarget as HTMLInputElement).files?.[0];
if (!file) return;
setUploading(true);
const filestack = await import('filestack-js');
const security = await fetch('/api/filestack-creds').then((r) => r.json());
const secured = filestack.init(import.meta.env.VITE_FILESTACK_API_KEY, { security });
setResult(await secured.upload(file));
setUploading(false);
}
The user experience is identical. 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:
<Show when={result()}>
<img
src={`https://cdn.filestackcontent.com/resize=width:600/${result().handle}`}
alt={result().filename}
/>
</Show>
The resize runs on the Filestack Processing Engine and the result is cached on the CDN, so your SolidStart server never touches the bytes. 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 component, one API route, and the real SDK. A signal pair drives the UI, the route signs short-lived policies with node:crypto, the app secret stays in process.env, and a resize is a URL built from the handle. The upload layer never leaks into the rest of your Solid code.
Start free. Sign up for an API key, drop in the component and the policy route above, and you have secured uploads and transformations running the same afternoon. When you are ready to scale, Filestack pricing tracks uploads, transformations, and bandwidth, so the bill follows real usage.
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 →