If you’ve looked at Filestack’s official SDKs, you’ve probably noticed there’s a React wrapper, an Angular wrapper, but nothing dedicated for Vue 3. That can feel like a blocker, but it really isn’t.
Filestack’s core JavaScript library, filestack-js, works in any JavaScript app. Vue 3 included. The framework wrappers are just thin layers on top of it. Once you understand that, integrating Filestack into Vue 3 takes about ten minutes.
This guide walks through the full setup using the Composition API, including how to wire up the Filestack picker, handle the upload response, and display the uploaded file in your app.
Why Use filestack-js Directly Instead of a Vue Wrapper
Quick context for anyone weighing the approach.
Filestack’s React and Angular wrappers handle a few framework-specific concerns: lifecycle integration, prop binding, and a component you can drop into your tree. They’re convenient, but they’re not the source of truth.
The source of truth is filestack-js, the official JavaScript SDK that all the wrappers depend on. For a broader walkthrough of integrating Filestack with JavaScript, this guide explains how the SDK, picker, and upload options work outside framework-specific wrappers.
For Vue 3, going straight to filestack-js gives you three things:
- One dependency to manage instead of two
- Full access to every picker option, transformation, and storage setting
- A clear mental model of what’s happening under the hood
The tradeoff is that you write a small wrapper component yourself. That component is short. We’ll build it together below.
What You’ll Need Before Starting
Before any code, make sure you have:
- A Vue 3 project (this guide uses Vite, but the setup works in any Vue 3 build)
- Node 18 or newer
- A free Filestack API key
If you’re starting fresh, spin up a new project in one command:
npm create vite@latest my-filestack-app — –template vue
cd my-filestack-app
npm install
That gives you a working Vue 3 app with Vite, ready for the next step.
Step 1: Install filestack-js
From your project root, run:
npm install filestack-js
That’s the entire dependency list. No additional Vue plugin needed.
Step 2: Store Your API Key in an Environment Variable
Vite reads environment variables from a .env file at the project root. Create one if it doesn’t exist:
# .env
VITE_FILESTACK_API_KEY=YOUR_API_KEY_HERE
A few notes on this. The VITE_ prefix is required for Vite to expose the variable to the client. Anything without that prefix stays server-side. Also worth saying upfront: Filestack API keys are designed to be public. They identify your application, not your account. For production apps, pair them with Filestack security policies so requests are signed and scoped.
For local development, the API key alone is enough.
Step 3: Create a Composable for the Filestack Client
Composables are how Vue 3 encourages you to share stateful logic between components. We’ll wrap the Filestack client in one.
Create a new file at src/composables/useFilestack.js:
import { ref } from 'vue'
import * as filestack from 'filestack-js'
const client = filestack.init(import.meta.env.VITE_FILESTACK_API_KEY)
export function useFilestack() {
const uploadedFile = ref(null)
const error = ref(null)
const isUploading = ref(false)
const openPicker = (options = {}) => {
const pickerOptions = {
maxFiles: 1,
accept: ['image/*'],
onUploadStarted: () => {
isUploading.value = true
error.value = null
},
onUploadDone: (res) => {
isUploading.value = false
if (res.filesUploaded.length > 0) {
uploadedFile.value = res.filesUploaded[0]
}
},
onFileUploadFailed: (file, err) => {
isUploading.value = false
error.value = err
},
...options,
}
client.picker(pickerOptions).open()
}
return {
uploadedFile,
error,
isUploading,
openPicker,
}
}
A few things worth pointing out here. The client is initialized once at module load, not inside the composable. That way every component using useFilestack shares the same client instance. The ref values are scoped to each component call, so two components using this composable get their own state. And the spread of …options at the bottom means any caller can override defaults without you having to expose every option as a separate parameter.
Step 4: Build the Upload Component
Now create a component that uses the composable. Add src/components/FileUploader.vue:
<script setup>
import { useFilestack } from '../composables/useFilestack'
const { uploadedFile, error, isUploading, openPicker } = useFilestack()
const handleUpload = () => {
openPicker({
maxFiles: 1,
accept: ['image/*', 'application/pdf'],
fromSources: ['local_file_system', 'googledrive', 'url'],
})
}
</script>
<template>
<div class="uploader">
<button
class="upload-btn"
:disabled="isUploading"
@click="handleUpload"
>
{{ isUploading ? 'Uploading...' : 'Upload a File' }}
</button>
<div v-if="error" class="error">
Upload failed: {{ error.message }}
</div>
<div v-if="uploadedFile" class="result">
<p><strong>File uploaded</strong></p>
<p>Filename: {{ uploadedFile.filename }}</p>
<p>Size: {{ uploadedFile.size }} bytes</p>
<p>
URL:
<a :href="uploadedFile.url" target="_blank">
{{ uploadedFile.url }}
</a>
</p>
<img
v-if="uploadedFile.mimetype.startsWith('image/')"
:src="uploadedFile.url"
:alt="uploadedFile.filename"
class="preview"
/>
</div>
</div>
</template>
<style scoped>
.uploader {
max-width: 480px;
margin: 2rem auto;
font-family: system-ui, sans-serif;
}
.upload-btn {
background: #ef4a25;
color: white;
border: none;
padding: 0.75rem 1.5rem;
font-size: 1rem;
border-radius: 6px;
cursor: pointer;
}
.upload-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.error {
margin-top: 1rem;
color: #b91c1c;
}
.result {
margin-top: 1.5rem;
padding: 1rem;
background: #f8fafc;
border-radius: 6px;
}
.preview {
max-width: 100%;
margin-top: 1rem;
border-radius: 6px;
}
</style>
The component does a few things. It calls useFilestack to get reactive state and the openPicker function. It passes its own picker options when the button is clicked, which override the defaults in the composable. And it renders the upload result, including a preview if the uploaded file is an image.
Step 5: Mount the Component
Open src/App.vue and replace the contents with:
<script setup>
import FileUploader from './components/FileUploader.vue'
</script>
<template>
<main>
<h1>Filestack + Vue 3 Demo</h1>
<FileUploader />
</main>
</template>
<style>
main {
max-width: 720px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
</style>
Run the dev server:
npm run dev
Open the URL Vite prints (usually http://localhost:5173), click the upload button, and the Filestack picker opens.
Step 6: Inspect the Upload Response
When a file finishes uploading, Filestack hands back a metadata object that looks like this:
{
"filename": "screenshot.png",
"handle": "apikey",
"mimetype": "image/png",
"originalPath": "screenshot.png",
"size": 184320,
"source": "local_file_system",
"url": "https://cdn.filestackcontent.com/handle",
"uploadId": "cfcc198e63b7328c17f09f1af519fcdf",
"status": "Stored"
}
The two fields you’ll use most often are handle and url. The handle is the unique identifier you use for transformations, deletions, and any further API calls. The URL is the CDN-served version of the file, ready to embed in your app.
Customizing the Picker for Your Use Case
The picker accepts a long list of options. A few you’ll reach for often.
Accept Specific File Types
Restrict what users can upload by MIME type or extension:
openPicker({
accept: ['image/jpeg', 'image/png', '.pdf'],
})
Add Cloud Sources
By default, the picker shows the local file system. You can extend it with cloud providers:
openPicker({
fromSources: [
'local_file_system',
'googledrive',
'dropbox',
'url',
],
})
Apply Transformations Before Upload
Want users to crop or rotate images before they hit your storage? Add a transformations block:
openPicker({
transformations: {
crop: {
aspectRatio: 16 / 9,
force: true,
},
rotate: true,
},
})
Upload Multiple Files
Set maxFiles to anything above 1 and the picker handles batch uploads. The onUploadDone callback returns an array in filesUploaded, so make sure your component handles more than one result.
openPicker({
maxFiles: 10,
})
The full options reference is in the Filestack picker documentation, and every option you see there works the same way through the filestack-js package.
Handling Errors and Edge Cases
A few things worth wiring up before this hits production.
Per-file failures
The onFileUploadFailed callback fires when a single file fails inside a batch. Other files in the same upload can still succeed. The composable above already captures this, but in a multi-file workflow you’ll want to track failures per-file rather than overwriting a single error state.
Network interruptions
Filestack retries automatically on transient failures. If a user closes the browser mid-upload, the upload is lost. For mission-critical workflows, look at resumable uploads, which the SDK supports through the client.upload method directly.
Security
For production, generate a security policy and signature on your backend, then pass them when initializing the client:
const client = filestack.init(API_KEY, {
security: {
policy: 'YOUR_POLICY',
signature: 'YOUR_SIGNATURE',
},
})
This locks down what the API key can do and prevents abuse. The policy and signature should always be generated server-side, never in the browser.
Wrapping Up
That’s the full integration. A composable, a component, and the official filestack-js SDK. No Vue-specific wrapper required.
The pattern works the same way whether you’re building a Vue 3 app, a Nuxt 3 site, or any other JavaScript framework where an official wrapper isn’t available. Initialize the client once, expose what you need through a composable or hook, and let your components stay focused on the UI.
If you’re ready to ship, get started with Filestack and drop the code above into your project.
Related reading:
A Product Marketing Manager at Filestack with four years of dedicated experience. As a true technology enthusiast, they pair marketing expertise with a deep technical background. This allows them to effectively translate complex product capabilities into clear value for a developer-focused audience.
Read More →