Crops API

How to check for, retrieve, and output crops in your template code, including lazy generation and the crop-or-fallback pattern.

Each crop in MediaHub is stored as its own ProcessWire page with its own ID, image file, preset key, and crop coordinates. This makes crops independently selectable in page reference fields and queryable in template code.

How crops are created

Crops can be created in four ways:

  1. Manually in the crop editor: an editor opens an asset's detail page, clicks Crop image, selects a preset, positions the crop, and chooses a save option. Save as crop creates a child crop linked to this asset; Save as new asset creates a standalone Library asset with its own metadata. Both can also be invoked from a card's freeform Crop image icon on a MediaHub field. See Image Cropping for the full UI walkthrough. The result of "Save as crop" from these entry points is a library crop.
  2. Automatically on upload: if you tick crop presets in the Upload Automation section of the module config, MediaHub generates a crop for each checked preset whenever a new raster image is uploaded, framed around the focus point when one has been moved from centre. These are library crops. See Configuration for details and hosting considerations.
  3. Automatically via inline crop badges: when a MediaHub field has crop presets configured, the page editor auto-generates a crop for each preset at render time, using the asset's focus point when set. The editor sees a filled badge and can click it to adjust the position. These are field-level crops — see below.
  4. Programmatically via the API: calling ensureCropImage() in template code creates a crop on first call (focus-aware since v1.17.0) and returns it on subsequent calls. The result is a library crop.

In all cases the result is a real crop page that can be adjusted by an editor at any time. ensureCropImage() never overwrites a crop that an editor has manually positioned. The difference is scope: library crops are global and visible in the asset's Crops table; field-level crops are scoped to a specific page and field.

Crops are accessed through the parent asset's API. The crop page itself also has convenience methods for its own image and metadata.

Library crops vs field-level crops

MediaHub distinguishes between two crop scopes:

Library cropField-level crop
Created byCrop editor, ensureCropImage()Inline crop badges on a page editor field
ScopeGlobal — visible everywhere the asset is referencedScoped to one page + field combination
Visible in Crops tableYesNo
Included in crops() / cropCount()YesNo
Selectable in the pickerYesNo
Internal markerref_type = libraryref_type = field

Field-level crops behave like per-page crops: each page gets its own independent crop for each preset, so an editor can position the crop differently on different pages without affecting the library. This is the same model used by per-page cropping modules, but applied selectively to inline badges only.

Library crops are the default. When you create a crop in the crop editor or call ensureCropImage() in template code, the result is always a library crop — shared, visible, and reusable.

Checking for crops

$asset->hasCrops();              // does this asset have any crops?
$asset->hasCrop('square');       // does a "square" crop exist?
$asset->hasCrop('16_9');         // does a "16:9" crop exist?
$asset->cropCount();             // how many crops?

Getting a crop image

cropImage() returns the crop's Pageimage directly, skipping the intermediate crop page. This is the most common way to use crops in template output.

// Get the square crop image at its original dimensions
$img = $asset->cropImage('square');

// Get it resized to specific dimensions
$img = $asset->cropImage('square', 400, 400);

// Resize by width only
$img = $asset->cropImage('16_9', 1200);

// Resize by height only
$img = $asset->cropImage('4_5', null, 600);

Returns null if no crop exists for that preset, if the asset is not a raster image, or if the asset is SVG.

Lazy crop generation with ensureCropImage()

ensureCropImage() returns the existing library crop if one exists, or auto-generates a library crop on first call. Call it in template code to guarantee a crop always exists for front-end output:

// Returns the existing square crop, or creates one framed around the focus point
$img = $asset->ensureCropImage('square', 400, 400);

// Works with any preset that has a defined ratio
$img = $asset->ensureCropImage('16_9', 1200, 675);

Focus-aware since v1.17.0. ensureCropImage() reads the focus point stored on the source image when generating a new crop and centres the crop window on it. If the focus point has not been moved from centre, generation uses a centred frame. No template code changes are required.

Returns null for SVG since v1.17.0. SVG (vector) assets cannot be raster-cropped — ensureCropImage(), cropImage(), and hasCrops() all return falsy values on SVG. Use the source image as a fallback: $asset->ensureCropImage('square') ?: $asset->image(). SVG renders at any size natively.

Key behaviours:

  • The generated crop is a library crop: it appears in the asset's Crops table in the admin, is independently selectable in the picker, and can be adjusted by an editor at any time.
  • Never overwrites an editor-curated crop. If an editor has already positioned the crop manually, that crop is returned unchanged.
  • Subsequent calls return the existing crop instantly without regenerating.
  • Returns null for freeform presets (no fixed ratio to calculate a crop from).
  • Returns null if the asset is SVG or if GD cannot process the file format.

createCenterCrop() renamed to createDefaultCrop() (v1.17.0)

If you call MediaHub::createCenterCrop() directly in custom module or hook code, rename it to createDefaultCrop(). A deprecated alias exists and will not break immediately, but it will be removed in a future release. The method signature is unchanged.

// Before (deprecated):
$cropPage = $mediaHub->createCenterCrop($asset, 'square');

// After:
$cropPage = $mediaHub->createDefaultCrop($asset, 'square');

Field-aware lookup with resolvedCropImage()

When your field uses inline crop badges, editors may have page-specific crops that differ from the library crop. resolvedCropImage() checks for a field-level crop first, falls back to the library crop, and auto-generates a library crop if neither exists:

// Lookup chain: field-level crop → library crop → auto-generate
$img = $asset->resolvedCropImage('square', $page->id, 'gallery', 400, 400);

// Without sizing
$img = $asset->resolvedCropImage('16_9', $page->id, 'hero_image');

This is the recommended method when you want to respect an editor's page-specific crop adjustment while still falling back to the library crop.

Field-level crop methods

These methods work with field-level crops directly. You rarely need them in template code — resolvedCropImage() handles the lookup chain automatically — but they're available for advanced use cases.

// Check if a field-level crop exists
$asset->hasFieldCrop('square', $page->id, 'gallery');

// Get the field-level crop page
$crop = $asset->getFieldCrop('square', $page->id, 'gallery');

// Get all field-level crops for this asset on a page+field
$crops = $asset->getFieldCrops($page->id, 'gallery');

Crop-or-fallback pattern

A common template pattern: use the crop if it exists, otherwise fall back to the source image resized to the same dimensions.

$asset = $page->hero_image;
if ($asset && $asset->id) {
    // Try the 16:9 crop first, fall back to a sized version of the original
    $img = $asset->cropImage('16_9', 1400, 788)
        ?: $asset->image()->size(1400, 788);
    echo "<img src='{$img->url}' alt='{$asset->altText()}'>";
}

Or use ensureCropImage() to guarantee a crop always exists:

$img = $asset->ensureCropImage('16_9', 1400, 788);
if ($img) {
    echo "<img src='{$img->url}' alt='{$asset->altText()}'>";
}

Working with crop pages directly

If you need the crop page itself (not just its image), use getCrop() or getCropsByPreset():

// Get the first crop page for a preset
$crop = $asset->getCrop('square');

if ($crop->id) {
    echo $crop->presetKey();      // "square"
    echo $crop->image()->url;     // URL of the crop image
    echo $crop->fileUrl();        // same as above
    echo $crop->altText();        // delegated from the master asset
}

// Get all crops for a preset (there may be more than one)
$crops = $asset->getCropsByPreset('16_9');

// Get all crops regardless of preset
$allCrops = $asset->crops();

Crop page methods

Crop pages (PkdMediahubCropPage) have their own API. Most metadata methods delegate to the master asset, so alt text, labels, and the about note stay consistent.

MethodReturnsDescription
$crop->image()Pageimage|nullThe crop image.
$crop->fileUrl()stringURL of the crop image.
$crop->sized(400, 400)Pageimage|nullResized crop image.
$crop->imgTag(400, 400)stringHTML <img> tag with alt text from the master asset.
$crop->master()PageThe source asset page this crop was derived from.
$crop->presetKey()stringThe preset key (e.g. "square", "16_9").
$crop->cropData()array|nullRaw crop coordinates: x, y, width, height, imagePixels.
$crop->altText()stringDelegated from the master asset.
$crop->labels()PageArrayDelegated from the master asset.
$crop->isImage()boolAlways true.
$crop->mimeType()stringDerived from the crop image's extension.
$crop->createdById()intUser ID of who created the crop.
$crop->createdAtIso()stringISO 8601 timestamp of when the crop was created.

Selecting crops in the page editor

When an editor clicks Choose on a MediaHub field, assets with crops show a crop badge on their tile. Clicking the tile opens a crop panel where the editor can select the original source image or any specific crop. Each selection is a distinct page reference, so a single field can hold a mix of source images and crops.

See Adding to Templates for how to configure inline crop badges on a field.

Built-in crop presets

Nine presets are available by default. Use the key value in template code:

KeyLabelRatio
freeformFreeformNo constraint
originalOriginalMatches the image's natural ratio
squareSquare1:1
16_916:916:9
4_54:54:5
5_75:75:7
4_34:34:3
3_53:53:5
3_23:23:2

Custom crop presets

You can create your own presets in Modules → Configure → MediaHub → Crop Presets. Each preset needs a Label, a Key (used in template code), and an optional Ratio in W:H format. Leave the ratio blank for a freeform crop.

Custom presets work identically to built-in ones in the API: use their key with cropImage(), ensureCropImage(), hasCrop(), and getCrop().

// A custom "hero_banner" preset at 16:5
$img = $asset->ensureCropImage('hero_banner', 1600, 500);

// A custom "instagram_story" preset at 9:16
$story = $asset->cropImage('instagram_story', 1080, 1920);

Grouping presets

Each custom preset can be assigned to a Group (e.g. "Social Media", "Editorial", "Front Page"). Presets with the same group name appear together in a collapsible section in the crop editor sidebar, making it easy to find the right preset when you have many. Groups are purely organisational and have no effect on the API.

See Configuration for the full list of built-in presets and the custom preset form.

Last updated