Media Types
The generatedStrapiMedia interface represents files in Strapi’s media library:
utils.ts
Copy
Ask AI
export interface StrapiMedia {
id: number;
documentId: string; // v5 only
name: string;
alternativeText: string | null;
caption: string | null;
width: number; // For images
height: number; // For images
formats: {
thumbnail?: StrapiMediaFormat;
small?: StrapiMediaFormat;
medium?: StrapiMediaFormat;
large?: StrapiMediaFormat;
} | null;
hash: string;
ext: string; // .jpg, .png, .pdf, etc.
mime: string; // image/jpeg, application/pdf, etc.
size: number; // File size in KB
url: string; // Full URL to the file
previewUrl: string | null;
provider: string; // local, cloudinary, s3, etc.
createdAt: string;
updatedAt: string;
}
export interface StrapiMediaFormat {
name: string;
hash: string;
ext: string;
mime: string;
width: number;
height: number;
size: number;
url: string;
}
Single vs Multiple Media Fields
Strapi media fields can be single or multiple:article.ts
Copy
Ask AI
import type { StrapiMedia } from '../utils';
export interface Article extends StrapiBaseEntity {
title: string;
// Single media field (nullable)
coverImage: StrapiMedia | null;
// Multiple media field (array)
gallery: StrapiMedia[];
}
Displaying Media
Display Images
Copy
Ask AI
---
import type { Article } from '@/strapi/collections/article/types';
interface Props {
article: Article;
}
const { article } = Astro.props;
---
{article.coverImage && (
<img
src={article.coverImage.url}
alt={article.coverImage.alternativeText || article.title}
width={article.coverImage.width}
height={article.coverImage.height}
/>
)}
Image Formats
Strapi automatically generates multiple sizes for images:- thumbnail
- small
- medium
- large
- original
Small preview (typically 156x156 or 245x156)
Copy
Ask AI
const thumb = image.formats?.thumbnail?.url;
Small size (max 500px)
Copy
Ask AI
const small = image.formats?.small?.url;
Medium size (max 750px)
Copy
Ask AI
const medium = image.formats?.medium?.url;
Large size (max 1000px)
Copy
Ask AI
const large = image.formats?.large?.url;
Original uploaded file
Copy
Ask AI
const original = image.url;
Not all formats are generated for every image. Small images may only have a thumbnail or no formats at all. Always check if a format exists before using it.
Display Non-Image Files
Document Download
Copy
Ask AI
---
const { document } = Astro.props;
---
{document && (
<a href={document.url} download>
<span>Download {document.name}</span>
<span class="size">{(document.size / 1024).toFixed(2)} MB</span>
<span class="type">{document.ext}</span>
</a>
)}
Uploading Files
strapi2front generates file upload helpers via the Strapi client.Upload Process
File uploads in Strapi are a two-step process:Generated Upload Helpers
The client includes typed file upload methods:client.ts
Copy
Ask AI
export interface StrapiFileInfo {
name?: string;
alternativeText?: string;
caption?: string;
}
export const files = {
/**
* Upload a file to Strapi media library
*/
async upload(file: File | Blob, options?: { fileInfo?: StrapiFileInfo }): Promise<StrapiMedia> {
const response = await strapiClient.files.upload(file, options);
return response;
},
/**
* Find files in media library
*/
async find(params?: Record<string, unknown>): Promise<StrapiMedia[]> {
const response = await strapiClient.files.find(params);
return Array.isArray(response) ? response : [];
},
/**
* Find one file by ID
*/
async findOne(fileId: number): Promise<StrapiMedia> {
const response = await strapiClient.files.findOne(fileId);
return response;
},
/**
* Update file metadata
*/
async update(fileId: number, fileInfo: StrapiFileInfo): Promise<StrapiMedia> {
const response = await strapiClient.files.update(fileId, fileInfo);
return response;
},
/**
* Delete a file
*/
async delete(fileId: number): Promise<StrapiMedia> {
const response = await strapiClient.files.delete(fileId);
return response;
},
};
Upload Examples
- Basic Upload
- Upload & Link
- Multiple Files
Copy
Ask AI
import { files } from '@/strapi/client';
async function uploadImage(file: File) {
// Upload file to media library
const uploadedFile = await files.upload(file, {
fileInfo: {
name: 'My Image',
alternativeText: 'Description of the image',
caption: 'Photo caption',
},
});
console.log('Uploaded:', uploadedFile);
console.log('File ID:', uploadedFile.id);
console.log('File URL:', uploadedFile.url);
return uploadedFile;
}
Copy
Ask AI
import { files } from '@/strapi/client';
import { articleService } from '@/strapi/collections/article/service';
async function createArticleWithImage(
articleData: { title: string; slug: string },
coverImageFile: File
) {
// Step 1: Upload the image
const uploadedImage = await files.upload(coverImageFile, {
fileInfo: {
alternativeText: articleData.title,
},
});
// Step 2: Create article with the image ID
const article = await articleService.create({
...articleData,
coverImage: uploadedImage.id, // Link using file ID
});
return article;
}
Copy
Ask AI
import { files } from '@/strapi/client';
import { articleService } from '@/strapi/collections/article/service';
async function uploadGallery(imageFiles: File[]) {
// Upload all files in parallel
const uploadPromises = imageFiles.map(file =>
files.upload(file, {
fileInfo: {
alternativeText: file.name,
},
})
);
const uploadedFiles = await Promise.all(uploadPromises);
// Get array of file IDs
const fileIds = uploadedFiles.map(f => f.id);
// Link to article
await articleService.update('article-123', {
gallery: fileIds,
});
return uploadedFiles;
}
Upload in Forms
Browser Form Upload
components/ArticleForm.astro
Copy
Ask AI
<form id="article-form">
<div>
<label for="title">Title</label>
<input type="text" id="title" name="title" required />
</div>
<div>
<label for="cover">Cover Image</label>
<input type="file" id="cover" name="cover" accept="image/*" />
</div>
<div>
<label for="gallery">Gallery (Multiple)</label>
<input type="file" id="gallery" name="gallery" accept="image/*" multiple />
</div>
<button type="submit">Create Article</button>
</form>
<script>
import { files } from '@/strapi/client';
import { articleService } from '@/strapi/collections/article/service';
const form = document.getElementById('article-form') as HTMLFormElement;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const title = formData.get('title') as string;
const coverFile = formData.get('cover') as File;
const galleryFiles = formData.getAll('gallery') as File[];
try {
// Upload cover image
let coverId = null;
if (coverFile && coverFile.size > 0) {
const uploadedCover = await files.upload(coverFile, {
fileInfo: { alternativeText: title },
});
coverId = uploadedCover.id;
}
// Upload gallery images
const galleryIds: number[] = [];
for (const file of galleryFiles) {
if (file.size > 0) {
const uploaded = await files.upload(file);
galleryIds.push(uploaded.id);
}
}
// Create article with uploaded files
const article = await articleService.create({
title,
slug: title.toLowerCase().replace(/\s+/g, '-'),
coverImage: coverId,
gallery: galleryIds,
});
console.log('Article created:', article);
window.location.href = `/blog/${article.slug}`;
} catch (error) {
console.error('Upload failed:', error);
}
});
</script>
React Form Upload
components/UploadForm.tsx
Copy
Ask AI
import { useState } from 'react';
import { files } from '@/strapi/client';
import { articleService } from '@/strapi/collections/article/service';
export function UploadForm() {
const [coverFile, setCoverFile] = useState<File | null>(null);
const [uploading, setUploading] = useState(false);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setCoverFile(file);
// Create preview
const url = URL.createObjectURL(file);
setPreviewUrl(url);
}
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setUploading(true);
try {
const formData = new FormData(e.currentTarget);
const title = formData.get('title') as string;
let coverId = null;
if (coverFile) {
const uploaded = await files.upload(coverFile, {
fileInfo: {
alternativeText: title,
caption: 'Article cover image',
},
});
coverId = uploaded.id;
}
const article = await articleService.create({
title,
slug: title.toLowerCase().replace(/\s+/g, '-'),
coverImage: coverId,
});
console.log('Success:', article);
} catch (error) {
console.error('Error:', error);
} finally {
setUploading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="title" required />
<div>
<input
type="file"
accept="image/*"
onChange={handleFileChange}
/>
{previewUrl && (
<img src={previewUrl} alt="Preview" width={200} />
)}
</div>
<button type="submit" disabled={uploading}>
{uploading ? 'Uploading...' : 'Create Article'}
</button>
</form>
);
}
Update Media Fields
- Replace Single Media
- Remove Single Media
- Add to Gallery
- Remove from Gallery
Copy
Ask AI
// Upload new image
const newImage = await files.upload(file);
// Replace existing cover image
await articleService.update('article-123', {
coverImage: newImage.id,
});
Copy
Ask AI
// Remove cover image
await articleService.update('article-123', {
coverImage: null,
});
Copy
Ask AI
// Get existing article
const article = await articleService.findOne('article-123', {
populate: ['gallery'],
});
// Upload new image
const newImage = await files.upload(file);
// Add to existing gallery
const currentIds = article.gallery.map(img => img.id);
await articleService.update('article-123', {
gallery: [...currentIds, newImage.id],
});
Copy
Ask AI
// Remove specific image from gallery
const article = await articleService.findOne('article-123', {
populate: ['gallery'],
});
const updatedIds = article.gallery
.filter(img => img.id !== imageIdToRemove)
.map(img => img.id);
await articleService.update('article-123', {
gallery: updatedIds,
});
File Validation
Validate files before upload:utils/validation.ts
Copy
Ask AI
export function validateImage(file: File): { valid: boolean; error?: string } {
// Check file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
if (!allowedTypes.includes(file.type)) {
return { valid: false, error: 'File must be an image (JPEG, PNG, WebP, or GIF)' };
}
// Check file size (max 5MB)
const maxSize = 5 * 1024 * 1024;
if (file.size > maxSize) {
return { valid: false, error: 'File must be smaller than 5MB' };
}
return { valid: true };
}
// Usage
const validation = validateImage(file);
if (!validation.valid) {
alert(validation.error);
return;
}
const uploaded = await files.upload(file);
Optimize Images
Use Strapi’s generated formats for optimal performance:components/OptimizedImage.astro
Copy
Ask AI
---
import type { StrapiMedia } from '@/strapi/utils';
interface Props {
image: StrapiMedia;
alt?: string;
sizes?: string;
}
const { image, alt, sizes = '100vw' } = Astro.props;
// Build srcset from available formats
const srcset = [
image.formats?.small && `${image.formats.small.url} ${image.formats.small.width}w`,
image.formats?.medium && `${image.formats.medium.url} ${image.formats.medium.width}w`,
image.formats?.large && `${image.formats.large.url} ${image.formats.large.width}w`,
`${image.url} ${image.width}w`,
].filter(Boolean).join(', ');
---
<img
src={image.formats?.medium?.url || image.url}
srcset={srcset}
sizes={sizes}
alt={alt || image.alternativeText || ''}
width={image.width}
height={image.height}
loading="lazy"
decoding="async"
/>
Copy
Ask AI
<OptimizedImage
image={article.coverImage}
alt={article.title}
sizes="(max-width: 768px) 100vw, 50vw"
/>