Guides
File Uploads
Accept file uploads, store them locally or in S3/Cloudinary, and display images.
1. Configure a storage adapter
// dyrected.config.ts (same for both frameworks)
import { LocalStorageAdapter } from '@dyrected/storage-local'
export default defineConfig({
storage: new LocalStorageAdapter({
uploadDir: './public/uploads',
staticUrlPrefix: '/uploads'
}),
// For production use S3 — see Configuring Storage guide
})See Configuring Storage for S3, R2, and Cloudinary setup.
2. Define an upload collection
// dyrected.config.ts (same for both frameworks)
{
slug: 'media',
upload: {
mimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
maxFileSize: 5 * 1024 * 1024,
imageSizes: [
{ name: 'thumbnail', width: 300, height: 300, fit: 'cover' },
{ name: 'card', width: 800 },
],
},
fields: [
{ name: 'alt', type: 'text' },
],
}3. Upload via the Admin UI
The Admin UI renders a drag-and-drop media browser for upload collections. Drop a file or click to select. Image sizes are generated server-side after upload.
4. Upload programmatically
import { createClient } from '@dyrected/sdk'
const client = createClient({
baseUrl: process.env.NEXT_PUBLIC_DYRECTED_URL!,
apiKey: process.env.DYRECTED_API_KEY!,
})
const form = new FormData()
form.append('file', file)
form.append('alt', 'My awesome image')
const media = await client.collection('media').upload(form)// Using raw fetch in a Client Component or Server Action
const form = new FormData()
form.append('file', file)
form.append('alt', file.name)
const res = await fetch(`${process.env.NEXT_PUBLIC_DYRECTED_URL}/collections/media`, {
method: 'POST',
headers: { 'x-api-key': process.env.DYRECTED_API_KEY! },
body: form,
})
const media = await res.json()// Using $fetch in a composable or component
const form = new FormData()
form.append('file', file)
form.append('alt', file.name)
const media = await $fetch('/dyrected/collections/media', {
method: 'POST',
headers: { 'x-api-key': useRuntimeConfig().public.dyrectedApiKey },
body: form,
})5. Attach media to another collection
// dyrected.config.ts (same for both frameworks)
{
slug: 'posts',
fields: [
{ name: 'title', type: 'text' },
{ name: 'coverImage', type: 'relationship', relationTo: 'media' },
],
}Fetch with depth: 1 to inline the media document:
// docs[0].coverImage.url → full storage URL
// docs[0].coverImage.sizes.thumbnail.url6. Render images
import Image from 'next/image'
export function PostCard({ post }: { post: any }) {
const cover = post.coverImage
if (!cover) return null
return (
<Image
src={cover.sizes?.card?.url ?? cover.url}
alt={cover.alt ?? ''}
width={800}
height={450}
/>
)
}Add external storage domains to
images.remotePatternsinnext.config.ts.
<!-- components/PostCard.vue -->
<script setup lang="ts">
defineProps<{ post: any }>()
</script>
<template>
<img
v-if="post.coverImage"
:src="post.coverImage.sizes?.card?.url ?? post.coverImage.url"
:alt="post.coverImage.alt ?? ''"
width="800"
height="450"
/>
</template>For Nuxt Image optimisation, use
<NuxtImg>from@nuxt/imageand add your storage domain toimage.domainsinnuxt.config.ts.