Images

Resizing, cropping, presets, srcset and base64 placeholders for image fields.

Declaring an image field

Declare a field of type image, then list it by name in the view:

// site/fields/hero/index.json
{ "label": "Hero", "type": "image", "options": {} }

// site/views/post/index.json
{ "fields": { "hero": {} } }

image decodes to a PageImage, a PageFile with transform and encoding helpers on top. SVGs are excluded even in an image field because the resize pipeline can't rasterize them; they decode to null. Use a file field for SVG.

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 exposes the original on first use and returns a cache-busted URL.

Transform options

src() takes a comma-separated option string, the same DSL 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 scales to fit, 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 colors
quality=60 JPEG quality (1-100), 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 from EXIF data, before any resize
trim=10 Remove border areas within this color-similarity tolerance
colorize=20:0:0 Adjust red:green:blue channel intensity
format=webp Encode as png, webp, etc. instead of JPEG
{$page->hero->src('w=800,h=400,fit=crop')}
{$page->hero->src('w=600,grayscale,blur=3')}
{$page->hero->src('orient,w=600')}
{$page->hero->src('rotate=90,flip=v')}

Each unique option string is its own cached variant.

Presets

Presets declared in config.json give a transform a name:

{
    "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"
        }
    }
}

A bare name with no = is a preset. Trailing key=value pairs override it, and a preset may reference other presets:

{$page->hero->src('card')}
{$page->hero->src('card,grayscale,quality=60')}

srcset()

Builds a srcset value from preset names (the width descriptor comes from the preset's w=) or from explicit option-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, for blur-up placeholders inlined in the first response. Unlike src(), it generates the variant immediately.

<img
    src="{$page->hero->base64('w=20,h=20,blur=5')}"
    data-src="{$page->hero->src('w=800')}"
    loading="lazy"
>

placeholder()

A global template function, not a field method. It builds a static SVG data URI with no source file, for a src fallback or a fixed-ratio box before any upload:

<img src="{placeholder(400, 300, '#e5e5e5')}" alt="">

Dimensions

width(), height(), ratio(), isLandscape(), isPortrait() and isSquare() read the original file. They are on the decoded PageImage, 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

src() and transform() only register a variant and return its URL. The variant is rendered when that URL is first requested (Page::route() checks the registry for a pending variant before running the page's router), and the web server serves the file statically after that.

If w/h don't shrink the source and no effects (including quality=) are requested, the original is mirrored as-is. Otherwise the variant is re-encoded as JPEG, unless format= asks for another, at quality= or the config default.

Config

{
    "images": {
        "driver": "gd",
        "quality": 80
    }
}

driver is the Intervention Image backend, gd or imagick (default imagick). quality is the JPEG quality (1-100) for re-encoded variants.

Example

{* 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>