Image Editor
The Image Editor component (<x-image-editor>) lets users select, crop, and optimize images directly in the browser before uploading them via Livewire. It integrates Pintura — a powerful, commercial image editing SDK — and wraps it into a simple Blade component.
The key advantage: all image processing (cropping, resizing, quality reduction, format conversion) happens client-side, which means the server never has to deal with oversized files. This is especially critical for:
- Laravel Vapor / AWS Lambda — Lambda has a hard 6 MB request payload limit. Users with high-resolution photos simply cannot upload them without client-side preprocessing.
- Server load reduction — instead of resizing on the server, the browser handles it and only uploads the final, optimized file.
- Better UX — users can crop their profile picture, adjust the framing, and preview the result before uploading.
Installation
Before using the Image Editor, you need to install and register Pintura in your project.
Step 1: Install Pintura via NPM
For development / testing, you can install the free trial version:
1npm i @pqina/pintura
Step 2: Register Pintura in your JavaScript
Import Pintura and its CSS in your main app.js file, and expose it on the window object:
1// resources/js/app.js2 3import * as Pintura from '@pqina/pintura';4import '@pqina/pintura/pintura.css';5 6window.Pintura = Pintura;
Step 3 (optional): Add Locale Support
Pintura supports multiple languages. To display the editor UI in a specific language (e.g. German), import the locale files and assign them to window.PinturaLocale:
1// resources/js/app.js2 3import * as Pintura from '@pqina/pintura';4import '@pqina/pintura/pintura.css';5import { LocaleCore, LocaleCrop } from '@pqina/pintura/locale/de_DE';6 7window.Pintura = Pintura;8window.PinturaLocale = { ...LocaleCore, ...LocaleCrop };
If window.PinturaLocale is set, the Image Editor component will automatically use it. If not set, Pintura defaults to English.
Basic Usage
At its simplest, the Image Editor is a button that opens a file picker. When the user selects an image, the Pintura editor opens as a modal overlay. After cropping/editing, the processed image is automatically uploaded to the Livewire component via wire:model.
1<x-image-editor wire:model="photo" label="Profile Picture" />
Circular Crop (Avatars)
Enable a circular crop mask — perfect for avatar or profile picture uploads. When circular is set to true, the editor automatically forces a 1:1 aspect ratio and overlays a circular mask as a visual guide.
1<x-image-editor2 wire:model="avatar"3 label="Avatar"4 :circular="true"5 :quality="0.8"6/>
Aspect Ratio
Lock the crop area to a specific aspect ratio. The user can still move and resize the crop area, but it will always maintain the given proportion.
Common ratios: 1 (square), 16/9 (widescreen), 4/3 (classic), 3/2 (photo), 2/1 (banner).
1<x-image-editor2 wire:model="banner"3 label="Banner Image"4 :aspect-ratio="16/9"5/>
Max Output Dimensions
Limit the output image dimensions with max-width and/or max-height (in pixels). The image will be downscaled to fit within these bounds while preserving its aspect ratio. Smaller images will not be upscaled.
1<x-image-editor2 wire:model="thumbnail"3 label="Thumbnail"4 :max-width="800"5 :max-height="600"6/>
Max File Size
The max-file-size attribute constrains the output file to a maximum size. The component will iteratively reduce the image until it fits:
- For lossy formats (JPEG, WebP): reduces quality first (steps: 0.7 → 0.55 → 0.4 → 0.3 → 0.2).
- If still too large, reduces dimensions in 10% steps (90% → 80% → ... → 10%).
- For lossless formats (PNG): skips quality reduction, goes directly to dimension reduction.
Accepted formats:
| Format | Example |
|---|---|
| Bytes (number) | 512000 |
| Kilobytes | "500KB" |
| Megabytes | "2MB" |
| Gigabytes | "1GB" |
1<x-image-editor2 wire:model="photo"3 label="Photo"4 max-file-size="500KB"5/>
Output Type & Quality
Control the output format and compression quality of the processed image.
The output-type attribute accepts any image MIME type: image/jpeg, image/png, image/webp. By default it is null, which means the original file format is preserved (a PNG stays a PNG, a JPEG stays a JPEG).
The quality attribute accepts a float between 0 and 1 (default: 0.82). This only affects lossy formats (JPEG, WebP). For PNG, the quality parameter is ignored.
1<x-image-editor 2 wire:model="photo" 3 label="Photo (WebP)" 4 output-type="image/webp" 5 :quality="0.9" 6/> 7 8<x-image-editor 9 wire:model="logo"10 label="Logo (PNG)"11 output-type="image/png"12/>
Pintura Utils (Editor Panels)
By default, only the crop utility is enabled. You can enable additional Pintura editing panels by passing an array of utility names via the utils attribute.
Available utilities (depends on your Pintura license):
| Utility | Description |
|---|---|
| crop | Crop, rotate, and flip the image |
| filter | Apply image filters (sepia, grayscale, etc.) |
| finetune | Adjust brightness, contrast, saturation, etc. |
| annotate | Draw, add text and shapes on the image |
| sticker | Add stickers to the image |
| redact | Redact parts of the image |
1<x-image-editor2 wire:model="photo"3 label="Photo"4 :utils="['crop', 'filter', 'finetune', 'annotate']"5/>
Button Customization
Customize the appearance and text of the trigger button using the following attributes:
| Attribute | Default | Description |
|---|---|---|
| button-type | white | The button style type (e.g. primary, danger, success, white, etc.). |
| button-size | null | Optional button size (sm, md, lg). |
| button-icon | false | Icon to display on the button (e.g. arrow-up-tray--outline). |
| button-text | Select image | Text shown before an image is selected. |
| button-edit-text | Edit image | Text shown after an image has been selected (re-edit mode). |
1<x-image-editor2 wire:model="photo"3 label="Upload Photo"4 button-type="primary"5 button-icon="arrow-up-tray--outline"6 button-text="Choose Image"7 button-edit-text="Edit Image"8/>
Sizes
The component field size can be set using the size attribute. This affects label and description spacing, consistent with other input components.
1<x-image-editor 2 wire:model="photo" 3 label="Photo" 4 size="sm" 5/> 6 7<x-image-editor 8 wire:model="photo" 9 label="Photo"10 size="md"11/>12 13<x-image-editor14 wire:model="photo"15 label="Photo"16 size="lg"17/>
Label Addons
Like other field components, you can add custom content next to the label using labelAddon.
1<x-image-editor2 wire:model="photo"3 label="Photo"4 labelAddon="<x-badge type='success'>New</x-badge>"5/>
Disabled State
Disable the component to prevent user interaction.
1<x-image-editor2 wire:model="photo"3 label="Photo"4 :disabled="true"5/>
Accepted File Types
Control which file types the file picker accepts using the accept attribute (standard HTML accept syntax). Default: image/jpeg, image/png, image/webp.
1<x-image-editor2 wire:model="photo"3 label="Photo"4 accept="image/png, image/webp"5/>
Loading States
Use wire:loading and wire:target to show a loading state on the component, just like on other Livewire-enabled components.
Additionally, the component automatically shows a spinner on the button while the file is being uploaded to Livewire.
1<x-image-editor2 wire:model="photo"3 label="Photo"4 wire:loading5 wire:target="save"6/>
All Attributes
Complete reference of all available attributes for the <x-image-editor> component:
| Attribute | Type | Default | Description |
|---|---|---|---|
| wire:model | String | — | Livewire property to bind the uploaded file to. Required for upload to work. |
| label | String | false | Label text displayed above the component. |
| labelAddon | HTML | false | Additional content next to the label (e.g. badges). |
| hint | String | false | Hint text displayed below the label. |
| description | String | false | Description text displayed below the button. |
| disabled | Boolean | false | Disables the file input and button. |
| size | String | md | Component size: sm, md, or lg. |
| aspect-ratio | Float | null | Locks the crop area to a specific aspect ratio (e.g. 16/9, 1). |
| max-width | Integer | null | Maximum output width in pixels. Image is downscaled to fit (no upscaling). |
| max-height | Integer | null | Maximum output height in pixels. Image is downscaled to fit (no upscaling). |
| quality | Float | 0.82 | Output compression quality (0–1). Only affects lossy formats (JPEG, WebP). |
| output-type | String | null | Output MIME type: image/jpeg, image/png, image/webp. When null, the original file format is preserved. |
| circular | Boolean | false | Enables circular crop mask (visual only). Forces 1:1 aspect ratio. |
| utils | Array | ['crop'] | Pintura editing panels to enable (e.g. crop, filter, finetune, annotate). |
| max-file-size | String|Integer | null | Maximum output file size (e.g. "500KB", "2MB", or bytes as integer). Client-side iterative reduction. |
| accept | String | image/jpeg, image/png, image/webp | Accepted file types for the file input (HTML accept syntax). |
| button-type | String | white | Button style type. |
| button-size | String | null | Button size. |
| button-icon | String | false | Icon for the button. |
| button-text | String | Select image | Button text before an image is selected. |
| button-edit-text | String | Edit image | Button text after an image is selected (re-edit mode). |
| show-errors | Boolean | true | Whether to display validation errors below the component. |
| x-on:upload-start | Alpine handler | — | Listener for the upload-start event (fires when upload begins). |
| x-on:upload-progress | Alpine handler | — | Listener for progress updates. Payload: { progress: Number }. |
| x-on:upload-finish | Alpine handler | — | Listener for the upload-finish event (fires on successful upload). |
| x-on:upload-error | Alpine handler | — | Listener for the upload-error event (fires when the upload fails). |
Livewire Integration Example
Here's a complete example of a Livewire component that uses the Image Editor for avatar uploads. Note that you need to use the WithFileUploads trait in your Livewire component, just like with a regular file upload.
1use Livewire\Component; 2use Livewire\WithFileUploads; 3 4class EditProfile extends Component 5{ 6 use WithFileUploads; 7 8 public $avatar; 9 10 public function save()11 {12 $this->validate([13 'avatar' => 'nullable|image|max:2048', // max 2 MB14 ]);15 16 if ($this->avatar) {17 $path = $this->avatar->store('avatars', 'public');18 auth()->user()->update(['avatar_path' => $path]);19 }20 21 // Reset the model → the Image Editor resets automatically22 $this->avatar = null;23 }24 25 public function render()26 {27 return view('livewire.edit-profile');28 }29}
Full Example (All Options)
Here's a comprehensive example that combines all major features — circular crop, dimension limits, file size constraint, output format, quality, and custom button styling:
1<x-image-editor 2 wire:model="avatar" 3 label="Profile Picture" 4 hint="Upload a square image for best results." 5 description="Accepted formats: JPEG, PNG, WebP. Max 2 MB." 6 :circular="true" 7 :aspect-ratio="1" 8 :max-width="512" 9 :max-height="512"10 :quality="0.85"11 max-file-size="2MB"12 button-type="primary"13 button-icon="camera--outline"14 button-text="Choose Avatar"15 button-edit-text="Re-crop Avatar"16/>
Upload Events (Alpine)
The Image Editor dispatches Alpine events during the upload lifecycle. You can listen to them using x-on: directives directly on the component. This is useful for showing spinners, disabling form buttons, or displaying success / error messages.
| Event | Payload | Description |
|---|---|---|
| upload-start | — | Fired when the upload to Livewire begins (after image processing is complete). |
| upload-progress | { progress: Number } | Fired during the upload with the current progress percentage (0–100). |
| upload-finish | — | Fired when the upload completes successfully. |
| upload-error | — | Fired when the upload fails. |
1<x-image-editor2 wire:model="avatar"3 label="Profile Picture"4 :circular="true"5 x-on:upload-start="console.log('Upload started…')"6 x-on:upload-progress="console.log('Progress:', $event.detail.progress + '%')"7 x-on:upload-finish="console.log('Upload complete!')"8 x-on:upload-error="alert('Upload failed – please try again.')"9/>
How It Works
A step-by-step overview of what happens when a user interacts with the Image Editor:
- The user clicks the button — a native file picker opens (filtered by the accept attribute).
- After selecting a file, the Pintura editor opens as a modal overlay with the configured options (crop, aspect ratio, utils, etc.).
- The user edits the image (crop, rotate, filter, etc.) and clicks "Done".
- Pintura outputs the processed image with the configured output-type, quality, max-width, and max-height constraints.
- If max-file-size is set and the output exceeds the limit, the component iteratively reduces the file (quality first, then dimensions) until it fits.
- The final image is uploaded to the Livewire component via $wire.upload() with progress tracking. Alpine events (upload-start, upload-progress, upload-finish, upload-error) are dispatched throughout the upload lifecycle.
- The button text switches to button-edit-text, allowing the user to re-edit the image.
- When the wire:model property is reset to null (e.g. after saving), the component automatically resets.
File Size Reduction Algorithm
When max-file-size is set and the processed image exceeds the limit, the component applies an iterative reduction algorithm:
For lossy formats (JPEG, WebP):
- Step 1: Reduce quality in steps: 0.7 → 0.55 → 0.4 → 0.3 → 0.2. If any step produces a file within the limit, it stops.
- Step 2: If quality reduction alone isn't enough, reduce dimensions in 10% steps: 90% → 80% → ... → 10%.
For lossless formats (PNG):
- Quality reduction is skipped (canvas toBlob ignores quality for PNG).
- Dimensions are reduced in 10% steps until the file fits.