Dyrected
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.url

6. 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.remotePatterns in next.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/image and add your storage domain to image.domains in nuxt.config.ts.

On this page