Automatically regenerate media sizes #1834
Replies: 10 comments 18 replies
-
How to delete old unused thumbnails? |
Beta Was this translation helpful? Give feedback.
-
I had to move payload init function into the regenerateMediaSizes async function. Because my config file was within the src directory I had to update the command to be
|
Beta Was this translation helpful? Give feedback.
-
@jmikrut : Is this script compatible with the cloud storage plugin? I removed the "filePath" section of the update call and it seems that every image in my collection is being iterated over but the sizes area is just empty objects: sizes: { w400: {}, w768: {}, w1024: {} }, |
Beta Was this translation helpful? Give feedback.
-
@andriasang I've managed to make a version of this script that works with plugin-cloud-storage – building on top of @ChristopherNowlan script and with a huge help from @jmikrut on Discord. The main point to get it working is: get the media Important to note as well: Check if you have the latest config file in your
|
Beta Was this translation helpful? Give feedback.
-
When I use @jmikrut's script*, images get lost in a random way. Often it works as expected, but sometimes the original file is removed during the process. * adapted to work with payload 1.6.27 async payload.init() |
Beta Was this translation helpful? Give feedback.
-
Hi, is there a problem running this under the current Payload (I'm on 1.15.7) ? I'm getting errors about not finding the collection. I've spent a while trying various things but still not working. If it does work, can someone explain where it should be run from etc please. Thanks |
Beta Was this translation helpful? Give feedback.
-
Here's a version based on @arieltonglet 's excellent script - also for plugin-cloud-storage . In this version only one image is processed at a time. For concurrency (with rate limits) and a larger number of files we'd use https://github.com/sindresorhus/p-limit This is a Typescript file, that can be placed anywhere, but must be run via ts-node in the root of the Payload project (at the same level as the .env file) For example... /* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
import payload from 'payload'
// eslint-disable-next-line
require('dotenv').config()
const { PAYLOAD_SECRET, DATABASE_URI } = process.env
const regenerateMediaSizes = async (): Promise<void> => {
await payload.init({
secret: PAYLOAD_SECRET,
mongoURL: DATABASE_URI,
local: true
})
const media = await payload.find({
collection: 'media',
depth: 0,
limit: 100
})
if (media != null && media.totalDocs > 0) {
payload.logger.info(`Found ${media.totalDocs} media files.`)
for (let index = 0; index < media.docs.length; index++) {
const mediaDoc = media.docs[index]
try {
await fetch(mediaDoc.url)
.then(async (response) => await response.blob())
.then(async (blob) => {
const arrayBuffer = await blob.arrayBuffer()
const buffer = Buffer.from(arrayBuffer, 'binary')
const file = {
data: buffer,
mimetype: blob.type,
name: mediaDoc.filename,
size: mediaDoc.filesize
}
await payload.update({
collection: 'media',
id: mediaDoc.id,
data: mediaDoc,
file,
overwriteExistingFiles: true,
contentType: blob.type
})
payload.logger.info(
`Media ${mediaDoc.id} (${mediaDoc.filename}) regenerated successfully`
)
})
} catch (err) {
payload.logger.error(`Media ${mediaDoc.id} (${mediaDoc.filename}) failed to regenerate`)
console.error(err)
}
}
} else {
payload.logger.info('No media files found.')
}
payload.logger.info('Done!')
process.exit(0)
}
void regenerateMediaSizes() |
Beta Was this translation helpful? Give feedback.
-
For Payload 3 we used: import { getPayload } from 'payload'
import * as dotenv from 'dotenv'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { importConfig } from 'payload/node'
dotenv.config() // Load environment variables from .env
const __dirname: string = path.dirname(fileURLToPath(import.meta.url))
const payloadConfigPath: string = path.join(__dirname, '../payload.config.ts')
const awaitedConfig = await importConfig(payloadConfigPath)
const payload = await getPayload({ config: awaitedConfig })
// eslint-disable-next-line
import 'dotenv/config'
const regenerateMediaSizes = async (): Promise<void> => {
const media = await payload.find({
collection: 'media',
depth: 0,
limit: 500,
})
if (media != null && media.totalDocs > 0) {
payload.logger.info(`Found ${media.totalDocs} media files.`)
for (let index = 0; index < media.docs.length; index++) {
const mediaDoc = media.docs[index]
const req = {
headers: new Map(), // Or use any object literal
} as PayloadRequestWithData
req.headers.set('origin', process.env.PAYLOAD_URL)
try {
await payload.update({
collection: 'media',
id: mediaDoc.id,
data: mediaDoc,
overwriteExistingFiles: true,
req,
})
payload.logger.info(
`Media ${mediaDoc.id} (${mediaDoc.filename}) successfully and new copy created.`,
)
} catch (err) {
payload.logger.error(`Media ${mediaDoc.id} (${mediaDoc.filename}) failed to regenerate`)
console.error(err)
}
}
} else {
payload.logger.info('No media files found.')
}
payload.logger.info('Done!')
process.exit(0)
}
void regenerateMediaSizes() |
Beta Was this translation helpful? Give feedback.
-
For Payload 2 I used something similar to the above approach - specifying However, it fails to regenerate a certain image size we have 😭 {
name: 'hero',
// ≈1.414:1
width: 2048,
height: 1448,
position: 'centre',
}, The other sizes were ok. I ended up having to dump all the affected ids, and manually go to the media entry in our running app admin page, and, one by one:
The middle part of this seemed necessary else the sizes would not regenerate |
Beta Was this translation helpful? Give feedback.
-
I'm currently using this as a migration with Payload v3, version 3.33.0 import { MigrateDownArgs, MigrateUpArgs } from '@payloadcms/db-mongodb'
import { Payload, UploadCollectionSlug, UploadConfig } from 'payload'
import fs from 'fs'
import path from 'path'
async function regenerateImageSizes(payload: Payload, collectionSlug: UploadCollectionSlug) {
// Get the collection config to access upload settings
const collectionConfig = payload.collections[collectionSlug].config
const uploadConfig = collectionConfig.upload as UploadConfig
// Get all documents that have files
const { docs } = await payload.find({
collection: collectionSlug,
depth: 0,
limit: 1000, // Adjust as needed
})
console.log(`Found ${docs.length} documents to process`)
// Update each document to trigger regeneration
for (const doc of docs) {
if (doc.filename) {
// Only process documents with files
console.log(`Processing document ${doc.id}...`)
try {
// Get the file path from PayloadCMS upload directory
const { staticDir } = uploadConfig
const filePath = path.join(staticDir!, doc.filename!)
// Check if file exists
if (!fs.existsSync(filePath)) {
console.error(`File not found: ${filePath}`)
continue
}
// Read the file as buffer
const fileBuffer = fs.readFileSync(filePath)
// Re-upload the file to trigger image processing
await payload.update({
collection: collectionSlug,
id: doc.id,
data: {
alt: doc.alt?.replace(/\.[\w]+$/, ''),
},
file: {
data: fileBuffer,
size: fs.statSync(filePath).size,
mimetype: doc.mimeType!,
name: doc.filename,
}
})
console.log(`Successfully regenerated sizes for document ${doc.id}`)
} catch (error) {
console.error(`Error processing document ${doc.id}:`, error)
}
}
}
console.log('Image regeneration complete!')
}
export async function up({ payload }: MigrateUpArgs): Promise<void> {
// Collection containing your uploads
const MEDIA_COLLECTIONS = ['media', 'brandbook'] as UploadCollectionSlug[]
// Process each collection
for (const collectionSlug of MEDIA_COLLECTIONS) {
await regenerateImageSizes(payload, collectionSlug).catch(console.error)
}
}
export async function down({}: MigrateDownArgs): Promise<void> {
console.log('No down migration needed for image regeneration')
} |
Beta Was this translation helpful? Give feedback.
-
This is a commonly asked topic and it just came up in Discord today, so I figured I would share a quick script that the team and I use for our Payload projects when we need something like WP's "Regenerate Thumbnails" plugin. Basically if you update your
imageSizes
and want to automatically re-run the image transformations for each one of your uploads, this can be super handy.If you save this file as
regenerateMediaSizes.js
, then all you need to do to run it isnode ./regenerateMediaSizes.js
and boom.Related: #434
Beta Was this translation helpful? Give feedback.
All reactions