With modern mobile devices—specifically iPhones—defaulting to the High Efficiency Image Container (HEIC) format, web applications increasingly receive .heic uploads. While HEIC offers superior compression and high dynamic range, native web browser support remains virtually non-existent. Serving HEIC files directly to web clients results in broken image renderers and degraded user experience.
Laravel provides native support for validating HEIC and AVIF mime types and extensions within its file validation API. However, accepting HEIC is only half the battle. To render these images across all platforms seamlessly, robust backend architecture is required to validate, offload, and asynchronously convert HEIC binaries into modern, web-ready formats like WebP or AVIF.
HEIC image decoding requires low-level C libraries. Standard ImageMagick or GD installations often lack HEIC delegation. Before writing PHP logic, ensure your server environment (or Docker container) has libheif installed alongside imagemagick.
# Ubuntu / Debian setup
sudo apt-get update && sudo apt-get install -y \
libheif-dev \
imagemagick \
php8.3-imagick
# Verify HEIC support in ImageMagick
identify -list format | grep -i heicUsing strict Form Requests prevents corrupt files and unhandled mime-types from reaching your processing pipeline. Use Laravel's standard File validation rule to specify supported extensions and maximum payload limits.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\File;
class UploadPhotoRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'photo' => [
'required',
File::types(['heic', 'heif', 'jpg', 'jpeg', 'png', 'webp', 'avif'])
->max('20mb'),
],
];
}
}Image processing is resource-heavy. Processing a 12-megapixel HEIC photo synchronously within an HTTP request lifecycle risks HTTP 504 timeouts and worker pool exhaustion. We encapsulate the image conversion logic into an actionable domain service using standard Intervention Image v3 with the Imagick driver.
<?php
namespace App\Services\Image;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Exception;
class ImageConverterService
{
protected ImageManager $manager;
public function __construct()
{
// Imagick driver is required for HEIC support
$this->manager = new ImageManager(new ImagickDriver());
}
/**
* Converts a source image to WebP format and uploads to target disk.
*/
public function convertToWebp(string $sourcePath, string $targetDirectory = 'photos'): string
{
$binaryData = Storage::disk('local')->get($sourcePath);
if (!$binaryData) {
throw new Exception("File not found at path: {$sourcePath}");
}
// Read raw image instance
$image = $this->manager->read($binaryData);
// Automatically rotate based on EXIF orientation metadata
$image->autoOrient();
// Encode to WebP format with optimal quality target
$encoded = $image->toWebp(quality: 82);
$fileName = $targetDirectory . '/' . Str::uuid() . '.webp';
// Save converted binary to public destination disk
Storage::disk('public')->put($fileName, (string) $encoded);
return $fileName;
}
}When a user uploads a photo, write the raw binary temporarily to a private disk and dispatch a background job to handle the CPU-heavy transformation.
<?php
namespace App\Jobs;
use App\Models\UserPhoto;
use App\Services\Image\ImageConverterService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
class ProcessUploadedPhotoJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
// Increase timeout for high-res photo manipulation
public int $timeout = 120;
public int $tries = 3;
public function __construct(
public UserPhoto $userPhoto,
public string $tempPath
) {}
public function handle(ImageConverterService $converter): void
{
try {
// Perform conversion
$convertedPath = $converter->convertToWebp($this->tempPath);
// Update Database record state
$this->userPhoto->update([
'path' => $convertedPath,
'status' => 'completed',
]);
} finally {
// Always clean up temporary upload storage
Storage::disk('local')->delete($this->tempPath);
}
}
}With validation and queueing architecture in place, the HTTP controller delegates the workload and responds immediately to the user client.
<?php
namespace App\Http\Controllers;
use App\Http\Requests\UploadPhotoRequest;
use App\Jobs\ProcessUploadedPhotoJob;
use App\Models\UserPhoto;
use Illuminate\Http\JsonResponse;
class PhotoUploadController extends Controller
{
public function __invoke(UploadPhotoRequest $request): JsonResponse
{
// Store temporary raw file locally
$tempPath = $request->file('photo')->store('temp-uploads', 'local');
// Create pending tracking entity
$photo = UserPhoto::create([
'user_id' => $request->user()->id,
'path' => null,
'status' => 'processing',
]);
// Push heavy lifting to queue worker
ProcessUploadedPhotoJob::dispatch($photo, $tempPath);
return response()->json([
'message' => 'Photo uploaded successfully and is being processed.',
'photo_id' => $photo->id,
'status' => $photo->status,
], 202);
}
}$image->autoOrient() during processing to avoid images rendering upside-down or sideways after conversion.php -d memory_limit=512M artisan queue:work).Accept: image/avif).File::types() API to validate HEIC uploads safely.libheif and imagemagick) are compiled inside server environments before enabling HEIC pipelines.