Show navigationHide navigation
Image
Media library picker with upload. Use for cover photos, avatars, product images, and any visual content. Images are managed as media entries with metadata (title for alt text, dimensions, blur placeholder).
Usage#
typescriptcoverImage: { label: 'Cover Image', format: 'image' },
Options#
| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | Must have an image selected |
hint | string | — | Helper text below the field |
How images work#
- Upload — in the editor, open the image picker. Upload a new image or select an existing one from the media library. Every upload requires a title (used as alt text).
- Storage — the entry JSON stores the media entry UUID, not a file path.
- Resolution —
query()automatically resolves the UUID to aResolvedImageFieldobject withsrc,alt,width,height, andblurDataURL. - Serving — images are served via the
/media/[uuid].[ext]route (local disk in dev, GitHub API in production). NoremotePatternsconfig needed. See Media library → Why a proxy route? for the reasoning.
Example: Team member with avatar#
Schema:
typescript// cms/octocms.config.ts teamMember: { label: 'Team Member', hasMany: true, fields: { name: { label: 'Name', format: 'string', entryTitle: true, required: true }, role: { label: 'Role', format: 'string' }, avatar: { label: 'Avatar', format: 'image', required: true }, }, },
Query + render:
tsx// src/app/team/page.tsx import Image from 'next/image'; import { query } from 'cms/__generated__/query'; export default async function TeamPage() { const members = await query('teamMember').toArray(); return ( <div className="grid grid-cols-3 gap-6"> {members.map((m) => ( <div key={m.sys.id} className="text-center"> <Image src={m.fields.avatar.src} alt={m.fields.avatar.alt} width={200} height={200} className="rounded-full mx-auto" {...(m.fields.avatar.blurDataURL ? { placeholder: 'blur' as const, blurDataURL: m.fields.avatar.blurDataURL } : {})} /> <h3>{m.fields.name}</h3> <p>{m.fields.role}</p> </div> ))} </div> ); }
Storage#
The entry JSON stores the media entry UUID:
json{ "avatar": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
The corresponding media entry lives at cms/content/media/media-[uuid].json with the image metadata (title, dimensions, blur data, original filename, folder).
Query result#
query() resolves the UUID to a ResolvedImageField:
typescriptconst member = await query('teamMember').first(); member.fields.avatar; // { // src: '/media/a1b2c3d4-e5f6-7890-abcd-ef1234567890.png', // alt: 'Alice headshot', ← from media entry title // width: 400, // height: 400, // blurDataURL: 'data:image/jpeg;base64,...' // }
Use src and alt directly with next/image or <img>. The blurDataURL enables instant blur-up placeholders.
For managing images in bulk, see Media library.