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:
- 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.
- 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.
- 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.
- 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 crop | Field-level crop | |
|---|---|---|
| Created by | Crop editor, ensureCropImage() | Inline crop badges on a page editor field |
| Scope | Global — visible everywhere the asset is referenced | Scoped to one page + field combination |
| Visible in Crops table | Yes | No |
Included in crops() / cropCount() | Yes | No |
| Selectable in the picker | Yes | No |
| Internal marker | ref_type = library | ref_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
nullfor freeform presets (no fixed ratio to calculate a crop from). - Returns
nullif 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.
| Method | Returns | Description |
|---|---|---|
$crop->image() | Pageimage|null | The crop image. |
$crop->fileUrl() | string | URL of the crop image. |
$crop->sized(400, 400) | Pageimage|null | Resized crop image. |
$crop->imgTag(400, 400) | string | HTML <img> tag with alt text from the master asset. |
$crop->master() | Page | The source asset page this crop was derived from. |
$crop->presetKey() | string | The preset key (e.g. "square", "16_9"). |
$crop->cropData() | array|null | Raw crop coordinates: x, y, width, height, imagePixels. |
$crop->altText() | string | Delegated from the master asset. |
$crop->labels() | PageArray | Delegated from the master asset. |
$crop->isImage() | bool | Always true. |
$crop->mimeType() | string | Derived from the crop image's extension. |
$crop->createdById() | int | User ID of who created the crop. |
$crop->createdAtIso() | string | ISO 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:
| Key | Label | Ratio |
|---|---|---|
freeform | Freeform | No constraint |
original | Original | Matches the image's natural ratio |
square | Square | 1:1 |
16_9 | 16:9 | 16:9 |
4_5 | 4:5 | 4:5 |
5_7 | 5:7 | 5:7 |
4_3 | 4:3 | 4:3 |
3_5 | 3:5 | 3:5 |
3_2 | 3:2 | 3: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