CDN & Storage
Upload files to global CDN storage with automatic optimization and fast delivery.
Upload Flow
File uploads use a two-step process for security and efficiency:
- Request a presigned upload URL from the API
- Upload the file directly to S3 using the presigned URL
- Confirm the upload is complete
Basic Upload
upload.ts
import { Stack0 } from '@stack0/sdk'const stack0 = new Stack0({apiKey: process.env.STACK0_API_KEY!})// Step 1: Get a presigned upload URLconst { uploadUrl, assetId, cdnUrl } = await stack0.cdn.getUploadUrl({filename: 'profile.jpg',mimeType: 'image/jpeg',size: file.size, // File size in bytes})// Step 2: Upload directly to S3await fetch(uploadUrl, {method: 'PUT',body: file,headers: {'Content-Type': 'image/jpeg',},})// Step 3: Confirm the uploadawait stack0.cdn.confirmUpload({ assetId })// The file is now available at cdnUrlconsole.log('File available at:', cdnUrl)
Upload Options
| Option | Type | Description |
|---|---|---|
| filename | string | Original filename (required) |
| mimeType | string | MIME type (required) |
| size | number | File size in bytes (required, max 100MB) |
| folder | string | Organize into folders |
| metadata | object | Custom metadata |
Organize with Folders
folders.ts
// Upload to a specific folderconst { uploadUrl, assetId, cdnUrl } = await stack0.cdn.getUploadUrl({filename: 'avatar.jpg',mimeType: 'image/jpeg',size: file.size,folder: 'users/avatars',})// Upload product imagesconst productUpload = await stack0.cdn.getUploadUrl({filename: 'hero.png',mimeType: 'image/png',size: file.size,folder: 'products/SKU-12345',})
Upload with Watermark
Automatically apply a watermark to images during upload. Great for branding photos, protecting images, or adding logos.
watermark.ts
// Upload with watermark from another CDN assetconst { uploadUrl, assetId, cdnUrl } = await stack0.cdn.getUploadUrl({filename: 'branded-photo.jpg',mimeType: 'image/jpeg',size: file.size,watermark: {assetId: 'your-logo-asset-id', // Reference another CDN assetposition: 'bottom-right',opacity: 50, // 50% transparentsizingMode: 'relative', // Size as percentage of main imagewidth: 15, // 15% of image widthoffsetX: 20, // 20px from edgeoffsetY: 20,},})// Or use a URL for the watermarkconst withUrlWatermark = await stack0.cdn.getUploadUrl({filename: 'photo.jpg',mimeType: 'image/jpeg',size: file.size,watermark: {url: 'https://example.com/logo.png',position: 'center',opacity: 30,tile: true, // Repeat watermark across imagetileSpacing: 150,},})
Watermark Options
| Option | Type | Description |
|---|---|---|
| assetId | string | ID of another CDN asset to use as watermark |
| url | string | Direct URL to watermark image (alternative to assetId) |
| position | string | Placement: top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right |
| sizingMode | "absolute" | "relative" | absolute = pixels, relative = percentage of main image |
| width / height | number | Size of watermark (pixels or percentage based on sizingMode) |
| opacity | number | Transparency 0-100 (default: 100 = fully opaque) |
| offsetX / offsetY | number | Pixel offset from position |
| rotation | number | Rotation angle -360 to 360 degrees |
| tile | boolean | Repeat watermark across entire image |
| tileSpacing | number | Spacing between tiles in pixels (default: 100) |
Get Asset Details
get-asset.ts
const asset = await stack0.cdn.get({ id: 'asset_xxx' })console.log('Filename:', asset.filename)console.log('Size:', asset.size, 'bytes')console.log('Type:', asset.type) // 'image' | 'video' | 'audio' | 'document' | 'other'console.log('CDN URL:', asset.cdnUrl)console.log('Dimensions:', asset.width, 'x', asset.height) // For imagesconsole.log('Created:', asset.createdAt)
List Assets
list-assets.ts
// List all assetsconst { assets, total, hasMore } = await stack0.cdn.list({limit: 50,offset: 0,})// Filter by folderconst userAvatars = await stack0.cdn.list({folder: 'users/avatars',})// Filter by typeconst images = await stack0.cdn.list({type: 'image',})// Search by filenameconst results = await stack0.cdn.list({search: 'hero',})// Sort by date or sizeconst recent = await stack0.cdn.list({sortBy: 'createdAt',sortOrder: 'desc',})
Update Asset
update-asset.ts
// Update asset metadataconst updated = await stack0.cdn.update({id: 'asset_xxx',filename: 'new-name.jpg',folder: 'archived',tags: ['product', 'hero'],alt: 'Product hero image',})
Move Assets
move-assets.ts
// Move multiple assets to a folderconst result = await stack0.cdn.move({assetIds: ['asset_xxx', 'asset_yyy', 'asset_zzz'],folder: 'archived/2024',})console.log(`Moved ${result.movedCount} assets`)// Move to root folderawait stack0.cdn.move({assetIds: ['asset_xxx'],folder: null, // null = root})
Delete Assets
delete-assets.ts
// Delete a single assetawait stack0.cdn.delete({ id: 'asset_xxx' })// Delete multiple assetsconst result = await stack0.cdn.deleteMany({ids: ['asset_xxx', 'asset_yyy', 'asset_zzz'],})console.log(`Deleted ${result.deletedCount} assets`)
Browser Upload Example
Complete example for handling file uploads in a React component:
components/file-upload.tsx
// Client component'use client'import { useState } from 'react'export function FileUpload() {const [uploading, setUploading] = useState(false)const [url, setUrl] = useState<string | null>(null)async function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {const file = e.target.files?.[0]if (!file) returnsetUploading(true)try {// Get upload URL from your APIconst res = await fetch('/api/upload', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({filename: file.name,mimeType: file.type,size: file.size,}),})const { uploadUrl, assetId, cdnUrl } = await res.json()// Upload to S3await fetch(uploadUrl, {method: 'PUT',body: file,headers: { 'Content-Type': file.type },})// Confirm uploadawait fetch('/api/upload/confirm', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ assetId }),})setUrl(cdnUrl)} finally {setUploading(false)}}return (<div><input type="file" onChange={handleUpload} disabled={uploading} />{uploading && <p>Uploading...</p>}{url && <img src={url} alt="Uploaded" />}</div>)}