Craft the perfect image

Crop Photos with Cropper.js

Let your users crop images with Cropper.js then grab the final image from PHP. Simple.

composer require symfony/ux-cropperjs

What Image Cropper gives you

  • A crop field

    A Symfony form type wrapping Cropper.js.

  • Fixed ratios

    Constrain the selection to the shape you need.

  • Preview included

    Users see the result before submitting.

  • Cropper.js options

    Every library option, set from PHP.

  • Validated output

    Coordinates land in your form data.

Form Tools for cropping images

Add Image Cropper to your app.

Form Type and tools for cropping images

// ... use statements hidden - click to show
use App\Service\UxPackageRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Asset\Packages;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\UX\Cropperjs\Factory\CropperInterface;
use Symfony\UX\Cropperjs\Form\CropperType;

class CropperjsController extends AbstractController
{
    public function __construct(
        private Packages $assets,
        #[Autowire('%kernel.project_dir%')] private string $projectDir,
    ) {
    }

    #[Route('/cropperjs', name: 'app_cropperjs')]
    public function __invoke(UxPackageRepository $packageRepository, CropperInterface $cropper, Request $request): Response
    {
        $package = $packageRepository->find('cropperjs');

        $crop = $cropper->createCrop($this->projectDir.'/assets/images/large.jpg');
        $crop->setCroppedMaxSize(1000, 750);

        $form = $this->createFormBuilder(['crop' => $crop])
            ->add('crop', CropperType::class, [
                'public_url' => $this->assets->getUrl('images/large.jpg'),
                'cropper_options' => [
                    'aspectRatio' => 4 / 3,
                    'preview' => '#cropper-preview',
                    'scalable' => false,
                    'zoomable' => false,
                ],
            ])
            ->getForm();

        $form->handleRequest($request);
        $croppedImage = null;
        $croppedThumbnail = null;
        if ($form->isSubmitted()) {
            // faking an error to let the page re-render with the cropped images
            $form->addError(new FormError('🤩'));
            $croppedImage = \sprintf('data:image/jpeg;base64,%s', base64_encode($crop->getCroppedImage()));
            $croppedThumbnail = \sprintf('data:image/jpeg;base64,%s', base64_encode($crop->getCroppedThumbnail(200, 150)));
        }

        return $this->render('ux_packages/cropperjs.html.twig', [
            'package' => $package,
            'form' => $form,
            'croppedImage' => $croppedImage,
            'croppedThumbnail' => $croppedThumbnail,
        ]);
    }
}
{% extends 'base.html.twig' %}

{% block body %}
    {% if croppedImage is null %}
        {{ form_start(form) }}
        <div class="grid grid-cols-12 gap-x-6">
            <div class="col-span-9">{{ form_widget(form) }}</div>
            <div class="col-span-3">
                <div id="cropper-preview" style="overflow: hidden;width: 200px;height: 200px;"></div>
            </div>
        </div>
            <twig:Button variant="dark" type="submit" class="mt-12 w-full">Crop it!</twig:Button>
        {{ form_end(form) }}
    {% else %}
        {# show the cropped image after submit! #}
        <div class="grid grid-cols-12 gap-x-6">
            <div class="col-span-9">
                <figure class="figure">
                    <img src="{{ croppedImage }}" class="figure-img img-fluid rounded" alt="Cropped image">
                    <figcaption class="figure-caption">The cropped image.</figcaption>
                </figure>
            </div>
            <div class="col-span-3">
                <figure class="figure">
                    <img src="{{ croppedThumbnail }}" class="figure-img img-fluid rounded" alt="Cropped thumbnail">
                    <figcaption class="figure-caption">A thumbnail of the cropped image.</figcaption>
                </figure>
            </div>
        </div>
    {% endif %}
{% endblock %}
Symfony logo

UX Cropper.js

A form type around Cropper.js

What the field gives you.

The visitor drags a selection, the form receives coordinates, and your code asks for the cropped file whenever it needs it.

A Symfony form type

It behaves like any other field: validation, form themes and data mapping all still apply.

$builder->add('crop', CropperType::class, [
    'public_url' => '/uploads/photo.jpg',
    'cropper_options' => [
        'aspectRatio' => 16 / 9,
    ],
]);

Cropper.js options

Whatever the library accepts is passed through to the controller, so its documentation stays valid.

'cropper_options' => [
    'aspectRatio' => 1,
    'viewMode' => 2,
    'guides' => false,
]

The cropped image

The Crop object carries the selection and can produce the cropped file or a thumbnail on demand.

$crop = $form->get('crop')->getData();

$thumbnail = $crop->getCroppedThumbnail(400, 400);

Install Image Cropper.

$ composer require symfony/ux-cropperjs

More from Symfony UX.

Image Cropper is one package among many. They share the same Stimulus foundation and install the same way.

Image for the Twig Components UX package

Twig Components

Create PHP classes that can render themselves

Image for the Live Components UX package

Live Components

Build dynamic interfaces with zero JavaScript

Image for the Turbo UX package

Turbo

Integration with Turbo for single-page-app and real-time experience

Browse every package

Keep Image Cropper at hand.

Documentation, the people behind it, and where to ask when something does not behave.