Media Upload
Strapi handles media files (images, documents, videos) through its Upload plugin. strapi2front generates types for media fields and provides patterns for file uploads.
Media Types
Section titled “Media Types”strapi2front generates the StrapiMedia type:
export interface StrapiMedia { id: number; documentId: string; name: string; alternativeText: string | null; caption: string | null; width: number | null; height: number | null; formats: StrapiMediaFormats | null; hash: string; ext: string; mime: string; size: number; url: string; previewUrl: string | null; provider: string; createdAt: string; updatedAt: string;}
export interface StrapiMediaFormats { thumbnail?: StrapiMediaFormat; small?: StrapiMediaFormat; medium?: StrapiMediaFormat; large?: StrapiMediaFormat;}
export interface StrapiMediaFormat { name: string; hash: string; ext: string; mime: string; width: number; height: number; size: number; url: string;}Reading Media
Section titled “Reading Media”Media fields are populated like relations:
const article = await articleService.findOne("abc123", { populate: ["cover", "gallery"],});
// Single mediaconsole.log(article.cover?.url);
// Multiple mediaarticle.gallery?.forEach(img => { console.log(img.url, img.width, img.height);});Responsive Images
Section titled “Responsive Images”Use the formats property for responsive images:
function ResponsiveImage({ media }: { media: StrapiMedia }) { const baseUrl = process.env.STRAPI_URL;
return ( <picture> {media.formats?.large && ( <source srcSet={`${baseUrl}${media.formats.large.url}`} media="(min-width: 1024px)" /> )} {media.formats?.medium && ( <source srcSet={`${baseUrl}${media.formats.medium.url}`} media="(min-width: 768px)" /> )} {media.formats?.small && ( <source srcSet={`${baseUrl}${media.formats.small.url}`} media="(min-width: 640px)" /> )} <img src={`${baseUrl}${media.url}`} alt={media.alternativeText || ""} width={media.width || undefined} height={media.height || undefined} /> </picture> );}Uploading Files
Section titled “Uploading Files”strapi2front provides two approaches for uploading files to Strapi. Enable the upload feature to generate helpers:
features: { upload: true,}Approach 1: Astro Action (Recommended)
Section titled “Approach 1: Astro Action (Recommended)”Server-side upload using Astro Actions. The private STRAPI_TOKEN stays on the server.
First, register the actions in src/actions/index.ts:
import { uploadAction, uploadMultipleAction } from '../strapi/shared/upload-action';
export const server = { upload: uploadAction, uploadMultiple: uploadMultipleAction,};Then use in your components:
import { actions } from 'astro:actions';
// Single file uploadconst handleUpload = async (file: File) => { const result = await actions.upload({ file, alternativeText: 'My image description', }); console.log('Uploaded:', result.data);};
// Multiple filesconst handleMultipleUpload = async (files: File[]) => { const result = await actions.uploadMultiple({ files, alternativeText: 'Gallery images', }); console.log('Uploaded:', result.data);};Approach 2: Public Upload Client
Section titled “Approach 2: Public Upload Client”Direct browser-to-Strapi uploads using a restricted public token.
Setup:
- Create a restricted token in Strapi Admin > Settings > API Tokens
- Set permissions: Upload > upload only (no delete, no update)
- Add to
.env:
PUBLIC_STRAPI_URL=http://localhost:1337PUBLIC_STRAPI_UPLOAD_TOKEN=your-restricted-upload-tokenUsage:
import { uploadFile, uploadFiles } from '@/strapi/shared/upload-client';
// Single fileconst media = await uploadFile(file, { alternativeText: 'Image description',});
// Multiple filesconst mediaArray = await uploadFiles(files, { caption: 'Gallery images',});Security Comparison
Section titled “Security Comparison”| Aspect | Astro Action | Public Client |
|---|---|---|
| Token exposure | Never (server-side) | Visible in browser |
| Token permissions | Full access | Upload only |
| Delete/update files | Yes | No |
| Works without backend | No | Yes |
Manual Upload (Custom Implementation)
Section titled “Manual Upload (Custom Implementation)”If you need custom upload logic:
async function uploadFile(file: File): Promise<number> { const formData = new FormData(); formData.append("files", file);
const response = await fetch(`${STRAPI_URL}/api/upload`, { method: "POST", headers: { Authorization: `Bearer ${STRAPI_TOKEN}`, }, body: formData, });
if (!response.ok) { throw new Error("Upload failed"); }
const [uploadedFile] = await response.json(); return uploadedFile.id;}Associating Media with Content
Section titled “Associating Media with Content”Media fields use numeric IDs (not documentIds). After uploading, use the returned id to associate media with content:
import { uploadFile } from '@/strapi/shared/upload-client';import { articleService } from '@/strapi/collections/article';
// Upload and get the media objectconst media = await uploadFile(coverImage);
// Create content with the file IDawait articleService.create({ title: "Article with Cover", content: "...", cover: media.id, // Numeric ID from upload response});import { actions } from 'astro:actions';import { articleService } from '@/strapi/collections/article';
// Upload via actionconst result = await actions.upload({ file: coverImage });
// Create content with the file IDawait articleService.create({ title: "Article with Cover", content: "...", cover: result.data.id,});import { uploadFiles } from '@/strapi/shared/upload-client';import { articleService } from '@/strapi/collections/article';
// Upload multiple filesconst mediaArray = await uploadFiles(galleryFiles);
// Extract IDsconst fileIds = mediaArray.map(m => m.id);
// Set galleryawait articleService.create({ title: "Article with Gallery", gallery: fileIds, // Array of numeric IDs});Updating Media
Section titled “Updating Media”import { uploadFile } from '@/strapi/shared/upload-client';
// Upload new fileconst newMedia = await uploadFile(newCover);
// Update the contentawait articleService.update("abc123", { cover: newMedia.id,});
// Remove media (set to null)await articleService.update("abc123", { cover: null,});Schema Validation
Section titled “Schema Validation”Generated Zod schemas validate media fields as numbers:
export const articleCreateSchema = z.object({ title: z.string(), // Single media (nullable) cover: z.number().int().positive().nullable(), // Multiple media (optional array) gallery: z.array(z.number().int().positive()).optional(),});Form Integration
Section titled “Form Integration”Example form with file upload using React Hook Form:
import { useState } from "react";import { useForm } from "react-hook-form";import { zodResolver } from "@hookform/resolvers/zod";import { articleCreateSchema, articleService } from "@/strapi/collections/article";import { uploadFile } from "@/strapi/shared/upload-client";
function CreateArticleForm() { const [uploading, setUploading] = useState(false); const form = useForm({ resolver: zodResolver(articleCreateSchema), });
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (!file) return;
setUploading(true); try { const media = await uploadFile(file, { alternativeText: file.name, }); form.setValue("cover", media.id); } finally { setUploading(false); } };
const onSubmit = async (data) => { await articleService.create(data); };
return ( <form onSubmit={form.handleSubmit(onSubmit)}> <input {...form.register("title")} />
<input type="file" accept="image/*" onChange={handleFileChange} disabled={uploading} /> {uploading && <span>Uploading...</span>}
{/* Hidden field for file ID */} <input type="hidden" {...form.register("cover")} />
<button type="submit" disabled={uploading}> Create Article </button> </form> );}Image URLs
Section titled “Image URLs”function getMediaUrl(media: StrapiMedia | null): string | null { if (!media) return null;
// If using external provider (S3, Cloudinary), URL is already absolute if (media.url.startsWith("http")) { return media.url; }
// Otherwise, prepend Strapi URL return `${process.env.STRAPI_URL}${media.url}`;}Upload Providers
Section titled “Upload Providers”Strapi supports various upload providers:
- Local - Files stored on server filesystem
- AWS S3 - Amazon S3 storage
- Cloudinary - Cloud-based image management
- Uploadcare - File uploading and delivery
The StrapiMedia type works with all providers. The url field will be absolute for cloud providers.