Storage
- @ductape/client
- React
- Vue 3
The storage service allows you to upload, download, list, and manage files in your Ductape product. Perfect for handling user uploads, documents, images, and any other file types.
Uploading Files
When using a publishable key, include session (token from your backend) in every storage request.
Basic Upload
const sessionToken = getSessionFromYourBackend();
const file = document.getElementById('fileInput').files[0];
const result = await ductape.storage.upload({
file: file,
path: 'uploads/documents',
session: sessionToken,
});
console.log('File URL:', result.url);
console.log('File key:', result.key);
Upload with Progress Tracking
const sessionToken = getSessionFromYourBackend();
const result = await ductape.storage.upload({
file: file,
path: 'uploads/images',
session: sessionToken,
onProgress: (progress) => {
console.log(`Progress: ${progress.percentage}%`);
console.log(`Uploaded: ${progress.loaded} / ${progress.total} bytes`);
// Update UI
document.getElementById('progress-bar').style.width =
`${progress.percentage}%`;
}
});
Upload with Custom Filename
const result = await ductape.storage.upload({
file: file,
path: 'uploads/documents',
filename: 'custom-name.pdf' // Override original filename
});
Upload with Metadata
const result = await ductape.storage.upload({
file: file,
path: 'uploads/documents',
metadata: {
userId: 'user-123',
uploadedAt: new Date().toISOString(),
category: 'invoices'
}
});
Upload Multiple Files
const files = Array.from(document.getElementById('fileInput').files);
const uploadPromises = files.map(file =>
ductape.storage.upload({
file: file,
path: 'uploads/batch',
onProgress: (progress) => {
console.log(`${file.name}: ${progress.percentage}%`);
}
})
);
const results = await Promise.all(uploadPromises);
console.log('All files uploaded:', results);
Downloading Files
Download File
const sessionToken = getSessionFromYourBackend();
const result = await ductape.storage.download({
storage: 'gcp-storage',
fileName: 'uploads/documents/report.pdf',
session: sessionToken,
});
Read File
readFile is an alias for download—use it when you want to read file content (e.g. for in-memory use or display). Same options and return type.
const result = await ductape.storage.readFile({
storage: 'gcp-storage',
fileName: 'uploads/documents/report.pdf',
session: sessionToken,
});
// result.data (base64), result.fileName, result.mimeType
Download as Data URL
const result = await ductape.storage.download({
key: 'uploads/images/photo.jpg'
});
const blob = new Blob([result.data], { type: result.contentType });
const dataUrl = await new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
// Use in img tag
document.getElementById('preview').src = dataUrl;
Listing Files
List All Files in Path
const result = await ductape.storage.list({
path: 'uploads/documents'
});
console.log('Files:', result.files);
result.files.forEach(file => {
console.log(`- ${file.key} (${file.size} bytes)`);
console.log(` Last modified: ${file.lastModified}`);
console.log(` Content type: ${file.contentType}`);
});
List with Pagination
const result = await ductape.storage.list({
path: 'uploads',
limit: 50,
offset: 0
});
console.log(`Showing ${result.files.length} of ${result.total} files`);
List with Prefix Filter
const result = await ductape.storage.list({
path: 'uploads',
prefix: '2024-01' // Only files starting with "2024-01"
});
Deleting Files
Delete Single File
const result = await ductape.storage.delete({
key: 'uploads/documents/old-file.pdf'
});
console.log('File deleted:', result.success);
Delete Multiple Files
const result = await ductape.storage.delete({
keys: [
'uploads/temp/file1.txt',
'uploads/temp/file2.txt',
'uploads/temp/file3.txt'
]
});
console.log(`Deleted ${result.deletedCount} files`);
Signed URLs
Generate temporary URLs for secure file access:
Get Signed URL for Download
const result = await ductape.storage.getSignedUrl({
key: 'uploads/documents/report.pdf',
operation: 'download',
expiresIn: 3600 // 1 hour in seconds
});
console.log('Temporary URL:', result.url);
// Share this URL - it expires after 1 hour
Get Signed URL for Upload
const result = await ductape.storage.getSignedUrl({
key: 'uploads/user-content/new-file.pdf',
operation: 'upload',
expiresIn: 300, // 5 minutes
contentType: 'application/pdf'
});
// Use the signed URL to upload directly from browser
const response = await fetch(result.url, {
method: 'PUT',
body: file,
headers: {
'Content-Type': 'application/pdf'
}
});
Complete Upload Example
Here's a complete example with file validation, progress tracking, and error handling:
async function uploadFile(fileInput: HTMLInputElement) {
const file = fileInput.files?.[0];
if (!file) {
alert('Please select a file');
return;
}
// Validate file
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
alert('File too large. Maximum size is 10MB');
return;
}
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!allowedTypes.includes(file.type)) {
alert('Invalid file type. Only JPEG, PNG, and PDF allowed');
return;
}
// Show progress UI
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
progressBar.style.display = 'block';
try {
const result = await ductape.storage.upload({
file: file,
path: `uploads/${new Date().getFullYear()}`,
metadata: {
originalName: file.name,
uploadedAt: new Date().toISOString(),
userId: 'current-user-id'
},
onProgress: (progress) => {
progressBar.value = progress.percentage;
progressText.textContent = `${progress.percentage}% uploaded`;
}
});
alert('Upload successful!');
console.log('File URL:', result.url);
// Save to database
await ductape.databases.insert({
table: 'files',
data: {
key: result.key,
url: result.url,
filename: file.name,
size: file.size,
contentType: file.type,
uploadedAt: new Date()
}
});
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed: ' + error.message);
} finally {
progressBar.style.display = 'none';
}
}
Image Upload with Preview
async function uploadImageWithPreview(fileInput: HTMLInputElement) {
const file = fileInput.files?.[0];
if (!file || !file.type.startsWith('image/')) {
alert('Please select an image file');
return;
}
// Show preview
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById('preview').src = e.target.result;
};
reader.readAsDataURL(file);
// Upload
try {
const result = await ductape.storage.upload({
file: file,
path: 'uploads/images',
onProgress: (progress) => {
console.log(`Uploading: ${progress.percentage}%`);
}
});
console.log('Image uploaded:', result.url);
// Update database with image URL
await ductape.databases.update({
table: 'users',
where: { id: 'current-user-id' },
data: { profileImage: result.url }
});
} catch (error) {
console.error('Upload failed:', error);
}
}
File Gallery Example
Building a complete file gallery with upload, list, and delete:
class FileGallery {
private ductape: Ductape;
private currentPath: string;
constructor(ductape: Ductape, path: string) {
this.ductape = ductape;
this.currentPath = path;
}
async uploadFile(file: File) {
return await this.ductape.storage.upload({
file: file,
path: this.currentPath,
onProgress: (progress) => {
this.updateUploadProgress(file.name, progress.percentage);
}
});
}
async loadFiles() {
const result = await this.ductape.storage.list({
path: this.currentPath,
limit: 100
});
this.renderFiles(result.files);
return result;
}
async deleteFile(key: string) {
if (!confirm('Are you sure you want to delete this file?')) {
return;
}
await this.ductape.storage.delete({ key });
await this.loadFiles(); // Refresh list
}
async downloadFile(key: string, filename: string) {
const result = await this.ductape.storage.download({ key });
const blob = new Blob([result.data], { type: result.contentType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
private renderFiles(files: any[]) {
const container = document.getElementById('file-list');
container.innerHTML = '';
files.forEach(file => {
const item = document.createElement('div');
item.className = 'file-item';
item.innerHTML = `
<div class="file-info">
<strong>${file.key.split('/').pop()}</strong>
<span>${this.formatFileSize(file.size)}</span>
<span>${new Date(file.lastModified).toLocaleDateString()}</span>
</div>
<div class="file-actions">
<button onclick="gallery.downloadFile('${file.key}', '${file.key.split('/').pop()}')">
Download
</button>
<button onclick="gallery.deleteFile('${file.key}')">
Delete
</button>
</div>
`;
container.appendChild(item);
});
}
private formatFileSize(bytes: number): string {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
private updateUploadProgress(filename: string, percentage: number) {
console.log(`${filename}: ${percentage}%`);
}
}
// Usage
const gallery = new FileGallery(ductape, 'uploads/documents');
await gallery.loadFiles();
Direct Browser Upload
Upload directly from browser to storage without going through your server:
async function directUpload(file: File) {
// 1. Get signed upload URL
const { url, key } = await ductape.storage.getSignedUrl({
key: `uploads/${Date.now()}-${file.name}`,
operation: 'upload',
expiresIn: 300,
contentType: file.type
});
// 2. Upload directly to storage
const response = await fetch(url, {
method: 'PUT',
body: file,
headers: {
'Content-Type': file.type
}
});
if (!response.ok) {
throw new Error('Upload failed');
}
// 3. Save metadata to database
await ductape.databases.insert({
table: 'files',
data: {
key: key,
filename: file.name,
size: file.size,
contentType: file.type,
uploadedAt: new Date()
}
});
console.log('File uploaded:', key);
}
Best Practices
- Validate files client-side: Check file size, type, and name before uploading
- Use progress callbacks: Provide visual feedback during upload
- Handle errors gracefully: Wrap operations in try-catch blocks
- Organize files: Use meaningful path structures (e.g.,
uploads/users/{userId}/documents) - Clean up old files: Implement periodic cleanup of unused files
- Use signed URLs: For sensitive files, use temporary signed URLs instead of public URLs
- Compress images: Resize/compress images before upload to save bandwidth
- Implement retries: Handle network failures with retry logic
File Type Detection
function getFileCategory(file: File): string {
const type = file.type;
if (type.startsWith('image/')) return 'images';
if (type.startsWith('video/')) return 'videos';
if (type.startsWith('audio/')) return 'audio';
if (type === 'application/pdf') return 'documents';
if (type.includes('spreadsheet') || type.includes('excel')) return 'spreadsheets';
return 'other';
}
// Usage
const file = fileInput.files[0];
const category = getFileCategory(file);
const result = await ductape.storage.upload({
file: file,
path: `uploads/${category}`
});
Next Steps
React hooks for working with Ductape Storage. Upload, download, and manage files with automatic progress tracking and error handling.
When using a publishable key, include session in every storage call (upload, list, download, delete, signed URL). Example: import { session } from './config'.
useReadFile
Read a file from disk (local File or Blob). Uses the browser FileReader API; does not call Ductape storage. Use this to read a user-selected file before uploading or processing (e.g. to get base64, show a preview, or validate content).
Basic usage
import { useReadFile } from '@ductape/react';
import { useState } from 'react';
function ReadFileExample() {
const [file, setFile] = useState<File | null>(null);
const { readFile, data, isLoading, error, reset } = useReadFile();
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const f = e.target.files?.[0];
setFile(f ?? null);
reset();
};
const handleRead = async () => {
if (!file) return;
const result = await readFile(file);
console.log('Read from disk:', result.fileName, result.mimeType, result.data.length, 'bytes (base64)');
};
return (
<div>
<input type="file" onChange={handleFileChange} />
<button onClick={handleRead} disabled={isLoading || !file}>
{isLoading ? 'Reading…' : 'Read file from disk'}
</button>
{error && <p className="error">{error.message}</p>}
{data && (
<p className="muted">
{data.fileName} — {data.mimeType} — {Math.round((data.data.length * 3) / 4)} bytes
</p>
)}
</div>
);
}
With custom fileName
When reading a Blob (no .name), pass a fileName in options:
const result = await readFile(blob, { fileName: 'export.json' });
useUpload
Upload files to Ductape Storage with progress tracking. Pass storage, fileName, data, and session (when using publishable key).
Basic Upload
import { useUpload } from '@ductape/react';
import { useState } from 'react';
import { session } from './config';
function FileUploader() {
const [file, setFile] = useState<File | null>(null);
const { upload, isUploading, progress, error } = useUpload();
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files?.[0]) {
setFile(e.target.files[0]);
}
};
const handleUpload = async () => {
if (!file) return;
await upload({
storage: 'gcp-storage',
fileName: `uploads/documents/${file.name}`,
data: file,
mimeType: file.type || 'application/octet-stream',
session,
});
};
return (
<div>
<input type="file" onChange={handleFileChange} />
<button onClick={handleUpload} disabled={isUploading || !file}>
{isUploading && progress ? `Uploading ${progress.percentage}%` : 'Upload'}
</button>
{error && <p className="error">{error.message}</p>}
</div>
);
}
Upload with Progress Bar
import { session } from './config';
function UploadWithProgress() {
const [file, setFile] = useState<File | null>(null);
const { upload, isUploading, progress } = useUpload();
const handleUpload = async () => {
if (!file) return;
await upload({
storage: 'gcp-storage',
fileName: `uploads/images/${file.name}`,
data: file,
mimeType: file.type,
metadata: {
uploadedBy: 'user-123',
category: 'profile-pictures'
},
session,
});
};
return (
<div>
<input
type="file"
accept="image/*"
onChange={(e) => setFile(e.target.files?.[0] || null)}
/>
<button onClick={handleUpload} disabled={isUploading}>
Upload
</button>
{isUploading && progress && (
<div className="progress-container">
<div
className="progress-bar"
style={{ width: `${progress.percentage}%` }}
/>
<span>{progress.percentage}%</span>
</div>
)}
</div>
);
}
Multiple File Upload
import { useUpload } from '@ductape/react';
import { session } from './config';
function MultiFileUploader() {
const [files, setFiles] = useState<FileList | null>(null);
const [uploads, setUploads] = useState<any[]>([]);
const { upload, isUploading } = useUpload();
const handleUpload = async () => {
if (!files) return;
for (let i = 0; i < files.length; i++) {
const result = await upload({
storage: 'gcp-storage',
fileName: `uploads/batch/${files[i].name}`,
data: files[i],
mimeType: files[i].type || 'application/octet-stream',
session,
});
setUploads(prev => [...prev, result]);
}
};
return (
<div>
<input
type="file"
multiple
onChange={(e) => setFiles(e.target.files)}
/>
<button onClick={handleUpload} disabled={isUploading}>
Upload {files?.length || 0} Files
</button>
<div>
<h3>Uploaded Files:</h3>
<ul>
{uploads.map((u, idx) => (
<li key={idx}>{u.fileName ?? u.url}</li>
))}
</ul>
</div>
</div>
);
}
useStorageDownload
Download files from Ductape Storage. Include session when using a publishable key.
import { useStorageDownload } from '@ductape/react';
import { session } from './config';
function FileDownloader({ fileName }: { fileName: string }) {
const { mutate, isLoading, progress } = useStorageDownload({
onSuccess: (blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName.split('/').pop() || 'download';
a.click();
URL.revokeObjectURL(url);
}
});
const handleDownload = () => {
mutate({ storage: 'gcp-storage', fileName, session });
};
return (
<button onClick={handleDownload} disabled={isLoading}>
{isLoading ? `Downloading ${progress}%` : 'Download'}
</button>
);
}
useStorageDelete
Delete files from storage. Include session when using a publishable key.
import { useStorageDelete } from '@ductape/react';
import { session } from './config';
function FileDeleter({ fileName, filename }: { fileName: string; filename: string }) {
const { mutate, isLoading } = useStorageDelete({
onSuccess: () => {
alert('File deleted successfully');
}
});
const handleDelete = () => {
if (confirm(`Delete ${filename}?`)) {
mutate({ storage: 'gcp-storage', fileName, session });
}
};
return (
<button onClick={handleDelete} disabled={isLoading}>
{isLoading ? 'Deleting...' : 'Delete'}
</button>
);
}
useStorageList
List files in storage with filtering and pagination. Include session when using a publishable key.
import { useStorageList } from '@ductape/react';
import { session } from './config';
function FileGallery() {
const { data, isLoading, refetch } = useStorageList({
storage: 'gcp-storage',
prefix: 'uploads/images',
limit: 20,
session,
});
if (isLoading) return <div>Loading files...</div>;
return (
<div>
<button onClick={() => refetch()}>Refresh</button>
<div className="file-grid">
{data?.files.map(file => (
<div key={file.id} className="file-card">
<img src={file.url} alt={file.filename} />
<p>{file.filename}</p>
<p>{(file.size / 1024).toFixed(2)} KB</p>
</div>
))}
</div>
</div>
);
}
With Filters
import { session } from './config';
function FilteredFileList() {
const [fileType, setFileType] = useState('image');
const { data, isLoading } = useStorageList({
storage: 'gcp-storage',
prefix: 'uploads',
limit: 50,
session,
});
return (
<div>
<select value={fileType} onChange={(e) => setFileType(e.target.value)}>
<option value="image">Images</option>
<option value="pdf">PDFs</option>
</select>
{isLoading ? (
<div>Loading...</div>
) : (
<ul>
{data?.files.map(file => (
<li key={file.id}>
{file.filename} - {new Date(file.createdAt).toLocaleDateString()}
</li>
))}
</ul>
)}
</div>
);
}
useStorageURL
Get a signed URL for a file. Include session when using a publishable key.
import { useStorageURL } from '@ductape/react';
import { session } from './config';
function SecureImageDisplay({ fileName }: { fileName: string }) {
const { data: url, isLoading } = useStorageURL({
storage: 'gcp-storage',
fileName,
expiresIn: 3600, // 1 hour
session,
});
if (isLoading) return <div>Loading...</div>;
return <img src={url} alt="Secure file" />;
}
Complete File Manager Example
import { useUpload, useStorageList, useStorageDelete } from '@ductape/react';
import { session } from './config';
interface FileItem {
name: string;
size?: number;
url?: string;
}
function FileManager() {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const { data: listResult, isLoading: isLoadingFiles, refetch } = useStorageList({
storage: 'gcp-storage',
prefix: 'uploads',
limit: 50,
session,
});
const { upload, isUploading, progress } = useUpload();
const { mutate: deleteFile } = useStorageDelete({
onSuccess: () => refetch()
});
const handleUpload = async () => {
if (!selectedFile) return;
await upload({
storage: 'gcp-storage',
fileName: `uploads/${selectedFile.name}`,
data: selectedFile,
mimeType: selectedFile.type || 'application/octet-stream',
session,
});
setSelectedFile(null);
refetch();
};
const handleDelete = (fileName: string) => {
if (confirm(`Delete ${fileName}?`)) {
deleteFile({ storage: 'gcp-storage', fileName, session });
}
};
return (
<div className="file-manager">
<div className="upload-section">
<h2>Upload File</h2>
<input
type="file"
onChange={(e) => setSelectedFile(e.target.files?.[0] || null)}
/>
<button
onClick={handleUpload}
disabled={!selectedFile || isUploading}
>
{isUploading && progress ? `Uploading ${progress.percentage}%` : 'Upload'}
</button>
</div>
<div className="files-section">
<h2>Files</h2>
{isLoadingFiles ? (
<div>Loading files...</div>
) : (
<table>
<thead>
<tr>
<th>Filename</th>
<th>Size</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{(listResult?.files ?? []).map((file: FileItem) => (
<tr key={file.name}>
<td>{file.name}</td>
<td>{file.size != null ? (file.size / 1024).toFixed(2) + ' KB' : '—'}</td>
<td>—</td>
<td>
{file.url && (
<a href={file.url} target="_blank" rel="noopener noreferrer">View</a>
)}
<button onClick={() => handleDelete(file.name)}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
);
}
Next Steps
Vue 3 composables for working with Ductape Storage. Upload, download, and manage files with reactive state.
useStorageUpload
Upload files to Ductape Storage with progress tracking.
Basic Upload
<script setup lang="ts">
import { ref } from 'vue';
import { useStorageUpload } from '@ductape/vue';
const file = ref<File | null>(null);
const { mutate, isLoading, progress, error } = useStorageUpload({
onSuccess: (data) => {
console.log('File uploaded:', data.url);
alert('Upload successful!');
}
});
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement;
if (target.files?.[0]) {
file.value = target.files[0];
}
};
const handleUpload = () => {
if (!file.value) return;
mutate({
file: file.value,
path: 'uploads/documents'
});
};
</script>
<template>
<div>
<input type="file" @change="handleFileChange" />
<button @click="handleUpload" :disabled="isLoading || !file">
{{ isLoading ? `Uploading ${progress}%` : 'Upload' }}
</button>
<p v-if="error" class="error">{{ error.message }}</p>
</div>
</template>
Upload with Progress Bar
<script setup lang="ts">
import { ref } from 'vue';
import { useStorageUpload } from '@ductape/vue';
const file = ref<File | null>(null);
const { mutate, isLoading, progress } = useStorageUpload({
onSuccess: (data) => {
console.log('Uploaded:', data);
file.value = null;
}
});
const handleUpload = () => {
if (!file.value) return;
mutate({
file: file.value,
path: 'uploads/images',
metadata: {
uploadedBy: 'user-123',
category: 'profile-pictures'
}
});
};
</script>
<template>
<div>
<input
type="file"
accept="image/*"
@change="file = ($event.target as HTMLInputElement).files?.[0] || null"
/>
<button @click="handleUpload" :disabled="isLoading">
Upload
</button>
<div v-if="isLoading" class="progress-container">
<div
class="progress-bar"
:style="{ width: `${progress}%` }"
/>
<span>{{ progress }}%</span>
</div>
</div>
</template>
<style scoped>
.progress-container {
margin-top: 1rem;
}
.progress-bar {
height: 4px;
background: #4CAF50;
transition: width 0.3s;
}
</style>
Multiple File Upload
<script setup lang="ts">
import { ref } from 'vue';
import { useStorageUpload } from '@ductape/vue';
const files = ref<FileList | null>(null);
const uploads = ref<any[]>([]);
const { mutate, isLoading } = useStorageUpload({
onSuccess: (data) => {
uploads.value.push(data);
}
});
const handleUpload = async () => {
if (!files.value) return;
for (let i = 0; i < files.value.length; i++) {
await mutate({
file: files.value[i],
path: 'uploads/batch'
});
}
};
</script>
<template>
<div>
<input
type="file"
multiple
@change="files = ($event.target as HTMLInputElement).files"
/>
<button @click="handleUpload" :disabled="isLoading">
Upload {{ files?.length || 0 }} Files
</button>
<div>
<h3>Uploaded Files:</h3>
<ul>
<li v-for="(upload, idx) in uploads" :key="idx">
{{ upload.filename }}
</li>
</ul>
</div>
</div>
</template>
useStorageDownload
Download files from Ductape Storage.
<script setup lang="ts">
import { useStorageDownload } from '@ductape/vue';
const props = defineProps<{ fileId: string }>();
const { mutate, isLoading, progress } = useStorageDownload({
onSuccess: (blob) => {
// Create download link
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'downloaded-file.pdf';
a.click();
URL.revokeObjectURL(url);
}
});
const handleDownload = () => {
mutate({ fileId: props.fileId });
};
</script>
<template>
<button @click="handleDownload" :disabled="isLoading">
{{ isLoading ? `Downloading ${progress}%` : 'Download' }}
</button>
</template>
useStorageDelete
Delete files from storage.
<script setup lang="ts">
import { useStorageDelete } from '@ductape/vue';
const props = defineProps<{
fileId: string;
filename: string;
}>();
const { mutate, isLoading } = useStorageDelete({
onSuccess: () => {
alert('File deleted successfully');
}
});
const handleDelete = () => {
if (confirm(`Delete ${props.filename}?`)) {
mutate({ fileId: props.fileId });
}
};
</script>
<template>
<button @click="handleDelete" :disabled="isLoading">
{{ isLoading ? 'Deleting...' : 'Delete' }}
</button>
</template>
useStorageList
List files in storage with filtering and pagination.
<script setup lang="ts">
import { useStorageList } from '@ductape/vue';
const { data, isLoading, refetch } = useStorageList({
path: 'uploads/images',
limit: 20
});
</script>
<template>
<div>
<button @click="refetch">Refresh</button>
<div v-if="isLoading">Loading files...</div>
<div v-else class="file-grid">
<div v-for="file in data?.files" :key="file.id" class="file-card">
<img :src="file.url" :alt="file.filename" />
<p>{{ file.filename }}</p>
<p>{{ (file.size / 1024).toFixed(2) }} KB</p>
</div>
</div>
</div>
</template>
With Filters
<script setup lang="ts">
import { ref } from 'vue';
import { useStorageList } from '@ductape/vue';
const fileType = ref('image');
const { data, isLoading } = useStorageList({
path: 'uploads',
filter: {
mimeType: fileType.value === 'image' ? 'image/*' : 'application/pdf'
},
orderBy: { field: 'createdAt', direction: 'desc' }
});
</script>
<template>
<div>
<select v-model="fileType">
<option value="image">Images</option>
<option value="pdf">PDFs</option>
</select>
<div v-if="isLoading">Loading...</div>
<ul v-else>
<li v-for="file in data?.files" :key="file.id">
{{ file.filename }} - {{ new Date(file.createdAt).toLocaleDateString() }}
</li>
</ul>
</div>
</template>
useStorageURL
Get a signed URL for a file.
<script setup lang="ts">
import { useStorageURL } from '@ductape/vue';
const props = defineProps<{ fileId: string }>();
const { data: url, isLoading } = useStorageURL({
fileId: props.fileId,
expiresIn: 3600 // 1 hour
});
</script>
<template>
<div v-if="isLoading">Loading...</div>
<img v-else :src="url" alt="Secure file" />
</template>
Complete File Manager Example
<script setup lang="ts">
import { ref } from 'vue';
import {
useStorageList,
useStorageUpload,
useStorageDelete
} from '@ductape/vue';
interface FileItem {
id: string;
filename: string;
size: number;
url: string;
createdAt: string;
}
const selectedFile = ref<File | null>(null);
// List files
const { data: files, isLoading: isLoadingFiles, refetch } = useStorageList({
path: 'uploads',
limit: 50
});
// Upload
const { mutate: upload, isLoading: isUploading, progress } = useStorageUpload({
onSuccess: () => {
selectedFile.value = null;
refetch();
}
});
// Delete
const { mutate: deleteFile } = useStorageDelete({
onSuccess: () => refetch()
});
const handleUpload = () => {
if (!selectedFile.value) return;
upload({
file: selectedFile.value,
path: 'uploads'
});
};
const handleDelete = (fileId: string, filename: string) => {
if (confirm(`Delete ${filename}?`)) {
deleteFile({ fileId });
}
};
</script>
<template>
<div class="file-manager">
<div class="upload-section">
<h2>Upload File</h2>
<input
type="file"
@change="selectedFile = ($event.target as HTMLInputElement).files?.[0] || null"
/>
<button
@click="handleUpload"
:disabled="!selectedFile || isUploading"
>
{{ isUploading ? `Uploading ${progress}%` : 'Upload' }}
</button>
</div>
<div class="files-section">
<h2>Files</h2>
<div v-if="isLoadingFiles">Loading files...</div>
<table v-else>
<thead>
<tr>
<th>Filename</th>
<th>Size</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="file in files?.files" :key="file.id">
<td>{{ file.filename }}</td>
<td>{{ (file.size / 1024).toFixed(2) }} KB</td>
<td>{{ new Date(file.createdAt).toLocaleDateString() }}</td>
<td>
<a :href="file.url" target="_blank" rel="noopener noreferrer">
View
</a>
<button @click="handleDelete(file.id, file.filename)">
Delete
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<style scoped>
.file-manager {
padding: 2rem;
}
.upload-section {
margin-bottom: 2rem;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 0.5rem;
text-align: left;
border-bottom: 1px solid #ddd;
}
button {
margin-left: 0.5rem;
}
</style>