Last active
April 24, 2025 21:47
-
-
Save elliott-w/19676d9373d79ee20b9195601dc45808 to your computer and use it in GitHub Desktop.
Payload CMS Image Aspect Ratios Pugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { | |
CollectionBeforeOperationHook, | |
FileData, | |
Plugin, | |
UploadCollectionSlug, | |
} from 'payload' | |
type AspectRatios = Record<string, number> | |
type Collections = Partial<Record<UploadCollectionSlug, AspectRatios>> | |
interface ImageAspectRatiosPluginArgs { | |
collections: Collections | |
enable?: boolean | |
} | |
export function imageAspectRatiosPlugin({ | |
collections, | |
enable = true, | |
}: ImageAspectRatiosPluginArgs): Plugin { | |
return (config) => { | |
if (!enable) { | |
return config | |
} | |
const uploadCollections = collections | |
const uploadCollectionSlugs = Object.keys(collections) | |
config.collections = (config.collections || []).map((c) => { | |
const slug = c.slug as UploadCollectionSlug | |
if (uploadCollectionSlugs.includes(c.slug) && slug in uploadCollections) { | |
return { | |
...c, | |
hooks: { | |
...(c.hooks || {}), | |
beforeOperation: [ | |
createBeforeOperationHook(uploadCollections[slug]!), | |
...(c.hooks?.beforeOperation || []), | |
], | |
}, | |
} | |
} else { | |
return c | |
} | |
}) | |
return config | |
} | |
} | |
type Dimensions = { | |
height: number | |
width: number | |
} | |
const createBeforeOperationHook = (aspectRatios: AspectRatios) => { | |
const beforeOperationHook: CollectionBeforeOperationHook = async ({ | |
req, | |
args, | |
collection, | |
operation, | |
}) => { | |
if (operation == 'update' || operation == 'create') { | |
let originalDimensions: Dimensions | undefined = undefined | |
if (req.file) { | |
const sharp = req.payload.config.sharp | |
const metadata = await sharp(req.file.data).metadata() | |
if (metadata.width && metadata.height) { | |
originalDimensions = { | |
width: metadata.width, | |
height: metadata.height, | |
} | |
} | |
} else { | |
const duplicateFromID: string | number | undefined = | |
args.duplicateFromID | |
if (duplicateFromID) { | |
const data = await req.payload.findByID({ | |
collection: collection.slug as UploadCollectionSlug, | |
id: duplicateFromID, | |
select: { | |
width: true, | |
height: true, | |
}, | |
}) | |
originalDimensions = { | |
width: data.width!, | |
height: data.height!, | |
} | |
} else { | |
const data = args.data as FileData | |
originalDimensions = { | |
width: data.width, | |
height: data.height, | |
} | |
} | |
} | |
if (originalDimensions) { | |
const originalWidth = originalDimensions.width | |
const originalHeight = originalDimensions.height | |
const originalAspectRatio = originalWidth / originalHeight | |
collection.upload.imageSizes = (collection.upload.imageSizes || []).map( | |
(size) => { | |
if (size.name in aspectRatios) { | |
const desiredAspectRatio = aspectRatios[size.name]! | |
const dimensions = | |
desiredAspectRatio > originalAspectRatio | |
? { | |
width: originalWidth, | |
height: Math.round(originalWidth / desiredAspectRatio), | |
} | |
: { | |
width: Math.round(originalHeight * desiredAspectRatio), | |
height: originalHeight, | |
} | |
return { | |
...size, | |
...dimensions, | |
} | |
} | |
return size | |
} | |
) | |
} | |
} | |
return args | |
} | |
return beforeOperationHook | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default buildConfig({ | |
collections: [{ | |
slug: 'media', | |
fields: [], | |
upload: { | |
adminThumbnail: 'thumbnail', | |
imageSizes: [ | |
{ | |
name: 'thumbnail', | |
width: 300, | |
height: 300, | |
}, | |
{ | |
name: 'landscape', | |
// Width and height can be anything, even omitted/undefined since | |
// they always get overwritten by the plugin on a per-image basis | |
width: 1, | |
height: 1, | |
}, | |
{ | |
name: 'portrait', | |
}, | |
], | |
}, | |
}], | |
plugins: [ | |
imageAspectRatiosPlugin({ | |
collections: { | |
media: { | |
portrait: 9 / 16, | |
landscape: 16 / 9, | |
}, | |
}, | |
}), | |
], | |
sharp, | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment