Advanced Image Cropper for Strapi logo

Advanced Image Cropper for Strapi

By Decrypt Social / Dipak Raghuwansi

A Strapi plugin that allows you to seamlessly crop, resize, and edit images without leaving the admin panel.

Latest version

v1.0.1

released August 6, 2026

npm install strapi-plugin-croppable-media

Strapi Plugin Croppable Media

Croppable Media adds a Strapi custom field for selecting one image and creating multiple editor-controlled crops from it.

It is useful for sites where the same image appears in several layouts, such as hero banners, cards, grids, social previews, and portrait slots.

Features

  • Custom JSON field for Croppable Media
  • Media library browsing and upload
  • Multiple configured crop ratios per image
  • Per-crop maximum output width
  • Configurable output file size guard
  • Admin dashboard for crop presets and limits

Installation

npm install strapi-plugin-croppable-media

Enable the plugin in config/plugins.ts.

export default () => ({
  'croppable-media': {
    enabled: true,
  },
});

For local plugin development, use resolve.

export default () => ({
  'croppable-media': {
    enabled: true,
    resolve: './src/plugins/croppable-media-v5',
  },
});

Configuration

Open the Croppable Media plugin page in the Strapi admin panel to manage configuration.

The dashboard stores settings in Strapi's plugin store. Values from config/plugins.ts are only used as initial defaults and fallback values.

cropSizes

Defines the crop tabs shown to editors.

cropSizes: [
  { label: 'hero', ratio: [21, 9], maxWidth: 2400 },
  { label: 'portrait', ratio: [3, 4], maxWidth: 900 },
]

Each crop size supports:

PropertyTypeRequiredDescription
labelstringyesFormat key saved in the field value.
ratio[number, number]yesCrop aspect ratio as width and height.
maxWidthnumbernoMaximum generated crop width.

maxFileSize

Maximum target crop data size before the plugin reduces output quality and width.

Default:

maxFileSize: 2000000

fallbackMaxWidth

Width used when the output optimizer needs to shrink a crop that does not have a crop-specific maxWidth.

Default:

fallbackMaxWidth: 1920

deletePreviousCrop

Deletes an existing provider file with the same crop hash before uploading the replacement crop.

This is enabled by default so re-cropping an existing crop keeps the same predictable hash and replaces the provider object. The crop hash changes only when a different source asset hash or field asset hash is used.

Default:

deletePreviousCrop: true

Set this to false if you want to keep previous provider objects instead of replacing them.

Field Usage

Add a custom field in Content-Type Builder.

Choose:

Custom fields -> Croppable Media

The field stores the selected upload file data plus generated crop formats in JSON.

Example field shape:

{
  "id": 1,
  "name": "image.webp",
  "url": "/uploads/image.webp",
  "formats": {
    "hero": {
      "url": "https://cdn.example.com/generated-hero.webp",
      "width": 2400,
      "height": 1028
    },
    "square": {
      "url": "https://cdn.example.com/generated-square.webp",
      "width": 1000,
      "height": 1000
    }
  }
}

Frontend Usage

Use the crop format that matches the layout. Each configured crop size is saved under image.formats by its label.

For example, this crop config:

cropSizes: [
  { label: 'hero', ratio: [21, 9], maxWidth: 2400 },
  { label: 'card', ratio: [4, 3], maxWidth: 1200 },
  { label: 'square', ratio: [1, 1], maxWidth: 1000 },
]

Creates crop URLs like:

entry.image.formats.hero.url;
entry.image.formats.card.url;
entry.image.formats.square.url;

getImageURL

Use a small formatter helper in the frontend so components do not need to know the fallback rules.

type CroppableImageFormat = {
  url?: string | null;
};

type CroppableImage = {
  url?: string | null;
  formats?: Record<string, CroppableImageFormat | undefined> | null;
};

type GetImageURLOptions = {
  baseURL?: string;
};

export const getImageURL = (
  image?: CroppableImage | null,
  format?: string,
  options: GetImageURLOptions = {}
) => {
  if (!image) {
    return '';
  }

  const url = format ? image.formats?.[format]?.url || image.url : image.url;

  if (!url) {
    return '';
  }

  if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) {
    return url;
  }

  const baseURL = options.baseURL || process.env.NEXT_PUBLIC_STRAPI_URL || '';

  return `${baseURL}${url}`;
};

Usage:

const heroURL = getImageURL(entry.image, 'hero');
const cardURL = getImageURL(entry.image, 'card');
const originalURL = getImageURL(entry.image);

If the requested crop does not exist, getImageURL falls back to image.url.

React Example

<img
  src={getImageURL(entry.image, 'hero')}
  alt={entry.image?.alternativeText || ''}
/>

Next.js Image Example

import Image from 'next/image';

<Image
  src={getImageURL(entry.image, 'card')}
  alt={entry.image?.alternativeText || ''}
  width={1200}
  height={900}
/>

Common Layout Mapping

const imageFormats = {
  hero: 'hero',
  listingCard: 'card',
  authorAvatar: 'square',
};

const src = getImageURL(entry.image, imageFormats.listingCard);

Development

npm install
npm run build
npm run watch

License

MIT

Submit your content

Share your work with the community and get it listed in the Strapi ecosystem for everyone to discover and use.

Submit
Submit your content