Images
Resizing, cropping, presets, srcset, and inline base64 placeholders for image fields, built on top of the file field's URL and metadata API.
Declaring an image field
Declare a field with type image in site/fields/{name}/index.json, same as any other field type. A view's own index.json references it by name under fields, without repeating the type:
// site/fields/hero/index.json
{ "label": "Hero", "type": "image", "options": {} }
// site/views/post/index.json
{ "fields": { "hero": {} } }
image decodes to a PageImage, a PageFile subclass with transform and encoding helpers layered on top of the base file API (url(), mime, size, ...). SVGs are excluded even when assigned to an image field: they're vector, not rasterizable by the resize pipeline, and decode to null. Reference an SVG through a file field instead.
Basic access
{$page->hero} {* original file's public URL *}
{$page->hero('w=400,h=300,crop')} {* transformed variant URL, shorthand for src() *}
Bare {$page->hero} calls url(), which mirrors the original file into the page's public directory the first time it's requested and returns its cache-busted URL. Calling the field like a function, {$page->hero(...)}, is shorthand for src(...), described below.
Transform options
src() (and the transform() it's built on) take a comma-separated option string, the same DSL convention used across PhlatPage:
| Option | Effect |
|---|---|
w=400 |
Max width in pixels |
h=300 |
Max height in pixels |
fit=crop |
Crop to exactly fill w x h, default contain, scale to fit within, no crop |
grayscale |
Desaturate |
invert |
Invert colors |
blur=5 |
Gaussian blur radius |
brightness=10 |
Brightness adjustment |
contrast=20 |
Contrast adjustment |
gamma=1.5 |
Gamma correction |
pixelate=8 |
Pixelate block size |
sharpen=5 |
Sharpen amount |
posterize=16 |
Reduce to at most this many distinct colors |
quality=60 |
JPEG encode quality (1-100) for this variant, overriding the config default |
rotate=90 |
Rotate clockwise by this many degrees |
flip=v |
Mirror the image, h (default) or v |
orient |
Auto-rotate upright using EXIF orientation data, before any resize |
trim=10 |
Remove border areas within this color-similarity tolerance |
colorize=20:0:0 |
Adjust red:green:blue channel intensity |
{$page->hero->src('w=800,h=400,fit=crop')}
{$page->hero->src('w=600,grayscale,blur=3')}
{$page->hero->src('w=600,quality=50')}
{$page->hero->src('w=600,posterize=16')}
{$page->hero->src('orient,w=600')}
{$page->hero->src('rotate=90,flip=v')}
Each unique option string maps to its own cached variant. transform() only registers the requested variant and returns its URL, nothing is decoded or resized until that URL is actually requested.
Presets
Named presets, declared in config.json, let templates reference a transform by name instead of repeating the option string:
// config.json
{
"images": {
"presets": {
"thumbnail": "w=150,h=150,crop",
"card": "w=400,h=300,crop",
"hero": "w=1600,h=600,crop",
"avatar": "w=80,h=80,crop,grayscale"
}
}
}
{$page->hero->src('card')}
If the given string matches a configured preset name, it's used in place of a raw option string. Otherwise it's parsed as one.
srcset()
Builds a srcset attribute value from either preset names (width descriptor inferred from the preset's w=) or explicit option-string to descriptor pairs:
<img
src="{$page->hero->src('card')}"
srcset="{$page->hero->srcset(['thumbnail', 'card', 'hero'])}"
>
<img srcset="{$page->hero->srcset(['w=400' => '400w', 'w=800' => '800w'])}">
base64()
Encodes a variant as a data: URI, useful for blur-up placeholders that need to be inline in the initial HTML response, with no extra request:
<img
src="{$page->hero->base64('w=20,h=20,blur=5')}"
data-src="{$page->hero->src('w=800')}"
loading="lazy"
>
Unlike src(), this generates the variant immediately, into the same public path a live request for it would produce, rather than deferring to first request, since the encoded bytes are needed right away.
placeholder()
A global template function, not a field method, it builds a static SVG data URI with no source file involved, so it isn't tied to any particular field's value. Useful as a src fallback while the real image loads, or for a fixed-aspect-ratio box before any file is uploaded:
<img src="{placeholder(400, 300, '#e5e5e5')}" alt="">
Dimensions and orientation
width(), height(), ratio(), isLandscape(), isPortrait(), and isSquare() read the original file's dimensions. They live on the decoded PageImage, not on the field itself, so call decode() first:
{var $hero = $page->hero->decode()}
{if $hero}
<div class="{if $hero->isPortrait()}max-w-sm{else}max-w-2xl{/if}">
<img src="{$hero->src('w=800')}" width="{$hero->width()}" height="{$hero->height()}">
</div>
{/if}
How variants are generated
A transform's variant is rendered the first time its URL is actually requested (Page::route() checks the registry for a pending variant before running the page's own router), not when src()/transform() is called. Once written, the file is served as a static asset by the web server on every later request, no PHP involved.
If the requested w/h don't require shrinking the source and no effects (including quality=) were requested, the original file is mirrored as-is (same format, no re-encode). Otherwise the variant is re-encoded as JPEG, regardless of the source format, at quality= if given or the config default otherwise.
Config
{
"images": {
"driver": "gd",
"quality": 80
}
}
driver selects the Intervention Image backend (gd or imagick). quality sets JPEG encode quality (1-100) for variants that get re-encoded.
Responsive card image with a blurred inline placeholder while the real variant loads
<figure class="relative aspect-[4/3] overflow-hidden rounded-box">
<img
src="{$page->hero->base64('w=24,h=18,blur=4')}"
data-src="{$page->hero->src('card')}"
srcset="{$page->hero->srcset(['card', 'hero'])}"
sizes="(min-width: 768px) 50vw, 100vw"
class="w-full h-full object-cover"
loading="lazy"
alt="{$page->title}"
>
</figure>