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>