React components and hooks for the Plugable File Management API.
npm install @plugable-io/react| Export | Type | Description |
|---|---|---|
PlugableProvider |
Component | Context provider for configuration |
Dropzone |
Component | Drag & drop file uploader |
FilePreview |
Component | Universal file preview (images + file type icons) |
FileImage |
Component | Image display with caching |
FileList |
Component | Paginated file list (render prop) |
useFiles |
Hook | File fetching with pagination |
usePlugable |
Hook | Access client and context |
Wrap your app with PlugableProvider. You can provide a getToken function or use a supported auth provider.
import { PlugableProvider } from '@plugable-io/react';
function App() {
return (
<PlugableProvider
bucketId="your-bucket-id"
authProvider="clerk"
clerkJWTTemplate="plugable" // optional
>
<YourApp />
</PlugableProvider>
);
}<PlugableProvider
bucketId="your-bucket-id"
authProvider="supabase"
>
<YourApp />
</PlugableProvider><PlugableProvider
bucketId="your-bucket-id"
getToken={async () => {
return await getAuthToken();
}}
>
<YourApp />
</PlugableProvider>File uploader with drag-and-drop support. Works with default UI or custom render function.
import { Dropzone } from '@plugable-io/react';
function ProfileUploader() {
return (
<Dropzone
metadata={{ category: 'avatar', userId: '123' }}
accept="image/*"
maxFiles={1}
onUploadComplete={(files) => {
console.log('Avatar uploaded:', files[0]);
}}
onUploadError={(error) => {
console.error('Upload failed:', error);
}}
/>
);
}import { Dropzone } from '@plugable-io/react';
function DocumentUploader() {
return (
<Dropzone
metadata={{ type: 'document' }}
accept=".pdf,.doc,.docx"
maxFiles={10}
onUploadComplete={(files) => console.log('Uploaded:', files)}
>
{({ isDragActive, isUploading, uploadProgress, openFileDialog, uploadedFiles }) => (
<div
onClick={openFileDialog}
className={`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer
${isDragActive ? 'border-blue-500 bg-blue-50' : 'border-gray-300'}`}
>
{isUploading ? (
<div className="space-y-2">
<p className="font-medium">Uploading...</p>
{Object.entries(uploadProgress).map(([name, progress]) => (
<div key={name} className="text-sm">
<span>{name}</span>
<div className="w-full bg-gray-200 rounded h-2 mt-1">
<div
className="bg-blue-500 h-2 rounded transition-all"
style={{ width: `${progress}%` }}
/>
</div>
</div>
))}
</div>
) : (
<>
<p className="font-medium">
{isDragActive ? 'Drop files here' : 'Drag & drop documents'}
</p>
<p className="text-sm text-gray-500 mt-1">or click to browse</p>
</>
)}
{uploadedFiles.length > 0 && (
<div className="mt-4 text-sm text-green-600">
β {uploadedFiles.length} file(s) uploaded
</div>
)}
</div>
)}
</Dropzone>
);
}| Prop | Type | Description |
|---|---|---|
bucketId |
string |
Override default bucket ID |
metadata |
Record<string, any> |
Metadata to attach to uploaded files |
accept |
string |
File types to accept (e.g., "image/*", ".pdf,.doc") |
maxFiles |
number |
Maximum number of files |
onUploadComplete |
(files: FileObject[]) => void |
Called when uploads complete |
onUploadError |
(error: Error) => void |
Called on upload error |
onProgressUpdate |
(fileName: string, progress: number) => void |
Progress callback |
children |
(props: DropzoneRenderProps) => ReactNode |
Custom render function |
className |
string |
CSS class name |
style |
CSSProperties |
Inline styles |
Fetch and paginate files with automatic refresh on uploads.
import { useFiles, FilePreview } from '@plugable-io/react';
function InvoiceList() {
const { files, isLoading, pagination, refresh } = useFiles({
metadata: { type: 'invoice' },
perPage: 10,
});
if (isLoading) return <div>Loading...</div>;
return (
<div>
<div className="grid grid-cols-4 gap-4">
{files.map((file) => (
<div key={file.id} className="border rounded p-3">
<FilePreview file={file} width={60} height={60} />
<p className="text-sm mt-2 truncate">{file.name}</p>
<div className="flex gap-2 mt-2">
<button onClick={() => file.delete()}>Delete</button>
<a href={file.download_url} download>Download</a>
</div>
</div>
))}
</div>
<div className="flex gap-2 mt-4">
<button
onClick={pagination.loadPreviousPage}
disabled={!pagination.hasPrevious}
>
Previous
</button>
<span>Page {pagination.current}</span>
<button
onClick={pagination.loadNextPage}
disabled={!pagination.hasNext}
>
Next
</button>
</div>
</div>
);
}function ImageGallery() {
const { files, isLoading } = useFiles({
mediaType: 'image',
perPage: 20,
});
return (
<div className="grid grid-cols-3 gap-4">
{files.map((file) => (
<FilePreview key={file.id} file={file} width="100%" height={200} objectFit="cover" />
))}
</div>
);
}function LazyFileList() {
const { files, isLoading, refresh } = useFiles({
metadata: { folder: 'reports' },
autoLoad: false, // Don't load on mount
});
return (
<div>
<button onClick={refresh}>Load Files</button>
{isLoading && <span>Loading...</span>}
{files.map((file) => (
<div key={file.id}>{file.name}</div>
))}
</div>
);
}| Option | Type | Default | Description |
|---|---|---|---|
metadata |
Record<string, any> |
- | Filter files by metadata |
mediaType |
string |
- | Filter by media type (image, video, etc.) |
perPage |
number |
20 |
Files per page |
startPage |
number |
1 |
Initial page number |
autoLoad |
boolean |
true |
Fetch on mount |
| Property | Type | Description |
|---|---|---|
files |
FileObject[] |
Array of files |
isLoading |
boolean |
Loading state |
pagination.current |
number |
Current page |
pagination.hasNext |
boolean |
Has next page |
pagination.hasPrevious |
boolean |
Has previous page |
pagination.loadNextPage |
() => void |
Go to next page |
pagination.loadPreviousPage |
() => void |
Go to previous page |
setPage |
(page: number) => void |
Jump to specific page |
refresh |
() => Promise<void> |
Refresh current page |
Universal file preview component. Displays images inline and shows file type icons for other files.
import { FilePreview } from '@plugable-io/react';
function FileCard({ file }) {
return (
<div className="flex items-center gap-3 p-3 border rounded">
<FilePreview file={file} width={48} height={48} />
<div>
<p className="font-medium">{file.name}</p>
<p className="text-sm text-gray-500">{file.content_type}</p>
</div>
</div>
);
}import { FilePreview } from '@plugable-io/react';
function CustomFilePreview({ file }) {
return (
<FilePreview
file={file}
width={80}
height={80}
objectFit="cover"
renderNonImage={(file) => (
<div className="flex flex-col items-center justify-center text-gray-500">
<span className="text-2xl">π</span>
<span className="text-xs mt-1">{file.name.split('.').pop()}</span>
</div>
)}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
file |
FileObject |
required | File to preview |
width |
number | string |
80 |
Preview width |
height |
number | string |
80 |
Preview height |
objectFit |
'contain' | 'cover' | 'fill' | 'none' | 'scale-down' |
'cover' |
Image fit mode |
className |
string |
- | CSS class name |
style |
CSSProperties |
- | Inline styles |
showExtension |
boolean |
true |
Show file extension for non-images |
renderNonImage |
(file: FileObject) => ReactNode |
- | Custom renderer for non-image files |
Display images with automatic URL fetching and caching.
import { FileImage } from '@plugable-io/react';
function Avatar({ file }) {
return (
<FileImage
file={file}
width={100}
height={100}
objectFit="cover"
borderRadius={50}
alt="User avatar"
onLoad={() => console.log('Loaded')}
/>
);
}Render prop component for file listing (alternative to useFiles hook).
import { FileList, FilePreview } from '@plugable-io/react';
function Documents() {
return (
<FileList metadata={{ type: 'document' }} perPage={20}>
{({ files, isLoading, hasMore, loadMore, error }) => (
<div>
{error && <p className="text-red-500">Error: {error.message}</p>}
{files.map((file) => (
<div key={file.id} className="flex items-center gap-3 p-2">
<FilePreview file={file} width={40} height={40} />
<span>{file.name}</span>
<button onClick={() => file.delete()}>Delete</button>
</div>
))}
{hasMore && (
<button onClick={loadMore} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Load More'}
</button>
)}
</div>
)}
</FileList>
);
}Each FileObject has built-in methods for updates and deletion:
// Update file metadata
await file.update({
metadata: { tags: ['important', 'reviewed'] }
});
// Delete file
await file.delete();
// Access properties
console.log(file.id); // Unique ID
console.log(file.name); // Filename
console.log(file.content_type); // MIME type
console.log(file.download_url); // Signed download URL
console.log(file.metadata); // Custom metadataComplete file manager with upload, list, and preview:
import {
PlugableProvider,
Dropzone,
useFiles,
FilePreview
} from '@plugable-io/react';
function FileManager() {
const { files, isLoading, pagination, refresh } = useFiles({
perPage: 12,
});
return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">File Manager</h1>
{/* Upload Section */}
<Dropzone
maxFiles={5}
onUploadComplete={(uploaded) => {
console.log('Uploaded:', uploaded);
// Files list auto-refreshes via event system
}}
className="mb-8"
/>
{/* Files Grid */}
{isLoading ? (
<div className="text-center py-8">Loading...</div>
) : (
<div className="grid grid-cols-4 gap-4">
{files.map((file) => (
<div key={file.id} className="border rounded-lg p-3">
<FilePreview
file={file}
width="100%"
height={120}
objectFit="cover"
/>
<p className="text-sm mt-2 truncate" title={file.name}>
{file.name}
</p>
<div className="flex gap-2 mt-2">
<a
href={file.download_url}
download
className="text-blue-500 text-sm"
>
Download
</a>
<button
onClick={() => file.delete()}
className="text-red-500 text-sm"
>
Delete
</button>
</div>
</div>
))}
</div>
)}
{/* Pagination */}
<div className="flex justify-center gap-4 mt-6">
<button
onClick={pagination.loadPreviousPage}
disabled={!pagination.hasPrevious}
className="px-4 py-2 border rounded disabled:opacity-50"
>
Previous
</button>
<span className="py-2">Page {pagination.current}</span>
<button
onClick={pagination.loadNextPage}
disabled={!pagination.hasNext}
className="px-4 py-2 border rounded disabled:opacity-50"
>
Next
</button>
</div>
</div>
);
}
// App wrapper
function App() {
return (
<PlugableProvider
bucketId="your-bucket-id"
authProvider="clerk"
>
<FileManager />
</PlugableProvider>
);
}All types are exported:
import type {
PlugableProviderProps,
AuthProvider,
DropzoneProps,
DropzoneRenderProps,
FilePreviewProps,
FileImageProps,
FileListProps,
FileListRenderProps,
UseFilesOptions,
UseFilesResult,
FileObject,
SearchOptions,
UpdateOptions,
} from '@plugable-io/react';MIT