Skip to main content

Vue

For a browser-side Vue app, the recommended pattern is pre-signed URL uploads: your backend (any language) issues a short-lived signed URL, and the browser PUTs the file directly to Filebase. Your access keys never touch the client.

Step 1 — Backend issues a pre-signed URL

Use whichever backend language you have. Here's a Node.js / Express example:

server/index.ts
import express from 'express';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({
endpoint: 'https://s3.filebase.io',
region: 'auto',
credentials: {
accessKeyId: process.env.FILEBASE_KEY!,
secretAccessKey: process.env.FILEBASE_SECRET!,
},
});

const app = express();
app.use(express.json());

app.post('/api/presign-upload', async (req, res) => {
const { filename, contentType } = req.body;

const key = `uploads/${Date.now()}-${filename}`;

const url = await getSignedUrl(
s3,
new PutObjectCommand({
Bucket: 'my-vue-app',
Key: key,
ContentType: contentType,
}),
{ expiresIn: 600 },
);

res.json({ url, key });
});

app.listen(3000);

For other languages, see the AWS SDK guides — every SDK can generate pre-signed URLs.

Step 2 — Vue uploader component

src/components/FileUploader.vue
<script setup lang="ts">
import { ref } from 'vue';

const progress = ref(0);
const status = ref<'idle' | 'uploading' | 'done' | 'error'>('idle');
const result = ref<string | null>(null);

async function upload(event: Event) {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) return;

status.value = 'uploading';

try {
// Get pre-signed URL from our backend
const presignRes = await fetch('/api/presign-upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename: file.name, contentType: file.type }),
});
const { url, key } = await presignRes.json();

// PUT directly to Filebase, with progress
await new Promise<void>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('PUT', url);
xhr.setRequestHeader('Content-Type', file.type);
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
progress.value = Math.round((e.loaded / e.total) * 100);
}
});
xhr.onload = () => (xhr.status === 200 ? resolve() : reject(new Error('Upload failed')));
xhr.onerror = () => reject(new Error('Upload failed'));
xhr.send(file);
});

result.value = `https://my-vue-app.s3.filebase.io/${key}`;
status.value = 'done';
} catch (err) {
status.value = 'error';
console.error(err);
}
}
</script>

<template>
<div>
<input type="file" @change="upload" :disabled="status === 'uploading'" />

<p v-if="status === 'uploading'">Uploading: {{ progress }}%</p>
<p v-if="status === 'done'">
Done — <a :href="result!" target="_blank">{{ result }}</a>
</p>
<p v-if="status === 'error'">Upload failed.</p>
</div>
</template>

CORS configuration

For browser uploads to work, the bucket must have a CORS policy that allows PUT from your app's origin. See configure CORS for an SPA.

A minimal policy:

{
"CORSRules": [
{
"AllowedMethods": ["PUT", "GET", "HEAD"],
"AllowedOrigins": ["https://your-app.com", "http://localhost:5173"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
}

What's next