Uploading Artifacts
Boards supports direct artifact uploads, allowing users to bring their own content into boards alongside AI-generated artifacts. This feature enables workflows like:
- Uploading reference images for image-to-image generation
- Adding source videos for video editing
- Importing audio files for processing
- Uploading text files as prompts or inputs
Supported Artifact Types
The upload system supports the following artifact types:
- Images: JPEG, PNG, GIF, WebP, BMP, SVG
- Videos: MP4, QuickTime (MOV), AVI, WebM, MPEG, MKV
- Audio: MP3, WAV, OGG, WebM, M4A
- Text: Plain text, Markdown, JSON, HTML, CSV
Upload Methods
Boards provides two upload methods:
1. File Upload (Multipart)
Direct file upload from the user's device via REST API endpoint.
Endpoint: POST /api/uploads/artifact
Advantages:
- Progress tracking support
- Large file handling
- Works with all file types
2. URL Download
Download artifacts from external URLs via GraphQL mutation.
Mutation: uploadArtifact
Advantages:
- No local file needed
- Works with web-hosted content
- Useful for automation
Using the Upload Hook
The @weirdfingers/boards package provides a useUpload hook for React applications:
import { useUpload, ArtifactType } from '@weirdfingers/boards';
function MyComponent() {
const { upload, isUploading, progress, error } = useUpload();
const handleFileUpload = async (file: File) => {
try {
const result = await upload({
boardId: 'your-board-id',
artifactType: ArtifactType.IMAGE,
source: file, // Can be File or URL string
userDescription: 'My uploaded image',
});
console.log('Upload complete:', result.id);
} catch (err) {
console.error('Upload failed:', err);
}
};
return (
<div>
<input
type="file"
onChange={(e) => e.target.files?.[0] && handleFileUpload(e.target.files[0])}
disabled={isUploading}
/>
{isUploading && <progress value={progress} max={100} />}
{error && <p>Error: {error.message}</p>}
</div>
);
}
Hook API
Parameters
interface UploadRequest {
boardId: string; // UUID of the target board
artifactType: ArtifactType; // Type of artifact (IMAGE, VIDEO, AUDIO, TEXT)
source: File | string; // File object or URL string
userDescription?: string; // Optional description
parentGenerationId?: string; // Optional parent generation UUID
}
Return Value
interface UploadHook {
upload: (request: UploadRequest) => Promise<UploadResult>;
isUploading: boolean; // True while upload in progress
progress: number; // Upload progress (0-100)
error: Error | null; // Error if upload failed
}
interface UploadResult {
id: string; // UUID of created generation
status: string; // 'completed' or 'failed'
storageUrl: string; // URL to access the artifact
}
Upload UI Component
The Baseboards example app includes a full-featured upload component with:
- Drag-and-drop support
- File picker
- URL input
- Clipboard paste (for images)
- Progress indication
- Error handling
See apps/baseboards/src/components/boards/UploadArtifact.tsx for the complete implementation.
Security Features
The upload system includes multiple security measures:
SSRF Protection
URL uploads are validated to prevent Server-Side Request Forgery (SSRF) attacks:
- ✅ Allowed:
http://andhttps://public URLs - ❌ Blocked:
localhost,127.0.0.1, private IP ranges (10.x.x.x, 192.168.x.x, 172.16-31.x.x) - ❌ Blocked: Link-local addresses (169.254.x.x, AWS metadata endpoint)
- ❌ Blocked: Non-HTTP schemes (
file://,ftp://, etc.)
File Type Validation
- MIME type validation ensures uploaded content matches the declared artifact type
- File extensions are checked against an allowlist
- Content-Type headers are verified
File Size Limits
Default maximum file size is 100MB (configurable via BOARDS_MAX_UPLOAD_SIZE).
Filename Sanitization
All filenames are sanitized to prevent:
- Path traversal attacks (
../, absolute paths) - Null byte injection
- Dangerous characters (
<>:"|?*)
File Size Configuration
Configure the maximum upload size in your backend .env:
# Allow uploads up to 500MB
BOARDS_MAX_UPLOAD_SIZE=524288000
Allowed extensions are configured in packages/backend/src/boards/config.py.
GraphQL Mutation
For URL-based uploads via GraphQL:
mutation UploadArtifactFromUrl($input: UploadArtifactInput!) {
uploadArtifact(input: $input) {
id
status
storageUrl
artifactType
generatorName
}
}
Variables:
{
"input": {
"boardId": "550e8400-e29b-41d4-a716-446655440000",
"artifactType": "IMAGE",
"fileUrl": "https://example.com/image.jpg",
"userDescription": "Reference image",
"parentGenerationId": null
}
}
REST API
For file uploads via REST:
curl -X POST http://localhost:8088/api/uploads/artifact \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "board_id=550e8400-e29b-41d4-a716-446655440000" \
-F "artifact_type=image" \
-F "file=@/path/to/image.jpg" \
-F "user_description=My upload"
Response:
{
"id": "650e8400-e29b-41d4-a716-446655440001",
"status": "completed",
"storageUrl": "http://localhost:8088/api/storage/...",
"artifactType": "image",
"generatorName": "user-upload-image"
}
Generator Naming Convention
Uploaded artifacts are stored as Generation records with a special generator_name pattern:
user-upload-image- Uploaded imagesuser-upload-video- Uploaded videosuser-upload-audio- Uploaded audio filesuser-upload-text- Uploaded text files
This allows uploads to integrate seamlessly with the generation lineage system.
Storage
Uploaded artifacts are stored using the configured storage provider:
- Local: Files stored in
BOARDS_STORAGE_LOCAL_BASE_PATH - S3: Uploaded to configured S3 bucket
- Supabase Storage: Uploaded to Supabase bucket
- Google Cloud Storage: Uploaded to GCS bucket
See the Storage documentation for configuration details.
Error Handling
The upload system provides detailed error messages for common issues:
| Error | Cause | Solution |
|---|---|---|
| "URL not allowed" | SSRF protection blocked the URL | Use a public HTTP/HTTPS URL |
| "Invalid file type" | MIME type doesn't match artifact type | Ensure file type matches declared type |
| "File size exceeds maximum" | File too large | Reduce file size or increase limit |
| "Permission denied" | User can't upload to board | User must be board owner or editor |
| "Board not found" | Invalid board ID | Check board ID is correct |
Best Practices
- Client-side validation: Validate file size and type before upload to improve UX
- Progress feedback: Always show upload progress for files >1MB
- Error recovery: Allow users to retry failed uploads
- Cancellation: Implement upload cancellation for large files
- Chunked uploads: For very large files (>100MB), consider implementing chunked uploads
Example: Drag-and-Drop Upload
import { useUpload, ArtifactType } from '@weirdfingers/boards';
import { useCallback, useState } from 'react';
function DragDropUpload({ boardId }: { boardId: string }) {
const { upload, isUploading, progress } = useUpload();
const [dragActive, setDragActive] = useState(false);
const handleDrop = useCallback(async (e: React.DragEvent) => {
e.preventDefault();
setDragActive(false);
const files = e.dataTransfer.files;
if (files.length > 0) {
const file = files[0];
// Determine artifact type from file MIME type
let artifactType = ArtifactType.IMAGE;
if (file.type.startsWith('video/')) {
artifactType = ArtifactType.VIDEO;
} else if (file.type.startsWith('audio/')) {
artifactType = ArtifactType.AUDIO;
}
await upload({ boardId, artifactType, source: file });
}
}, [upload, boardId]);
return (
<div
onDrop={handleDrop}
onDragOver={(e) => { e.preventDefault(); setDragActive(true); }}
onDragLeave={() => setDragActive(false)}
className={dragActive ? 'border-blue-500' : 'border-gray-300'}
style={{ border: '2px dashed', padding: '2rem', textAlign: 'center' }}
>
{isUploading ? (
<div>Uploading... {Math.round(progress)}%</div>
) : (
<div>Drag and drop files here</div>
)}
</div>
);
}
Related Documentation
- Storage Configuration - Configure storage backends
- Generator UI - Use uploaded artifacts as generator inputs
- Artifact Types - Learn about artifact types