Hello,
I have some hooks that I would like all images to execute going forward.
Only issues is, if I want to execute those hooks on the existing documents in the collection then I have to go to the admin panel, save, go back, next, save go back, next, save go back....
Is there any way I can script this out?
Hey @richardvanbergen — I have the perfect solution for you!
You can boot Payload in local
mode, which is perfect for running seed scripts, migrations, and more. There is an example of how to do this here:
https://github.com/payloadcms/nextjs-custom-server/blob/master/seed/index.js
Locally booted Payload completely dodges all HTTP activity like opening the REST and GraphQL APIs, and you can use the Local API to do whatever you want (including resaving all of your images so their hooks run.)
Would this help?
Hey @jmikrut thanks for the tip. I did a very quite script just to see and I have a couple of issues.
The first is that it doesn't seem to want to read my .env into process.env
so I had to hardcode the variables. This is fine for now because this is just a throwaway script but I was just wondering if you've seen this behavior before.
The second and much bigger issue is that the local API doesn't seem to be executing any of the hooks.
Bellow is my script. The hooks on that collection doesn't seem to be called at all but I know it works in the admin panel.
I'm also using the server-side hook webpack hack described here as it involves image manipulation with sharp
.
import payload from 'payload'
import dotenv from 'dotenv'
import { MediaImageField } from '../fields/media'
const result = dotenv.config({
path: '../../.env',
})
console.log(result)
payload.init({
secret: '...',
mongoURL: '...',
local: true,
})
const resaveImageCollection = async () => {
const mediaImages = await payload.find({
collection: 'media-image',
})
mediaImages.docs.forEach(async (image: MediaImageField) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, ...imageData } = image
await payload.update({
id: image.id,
collection: 'media-image',
data: {
...imageData,
},
})
})
console.log('Seed completed!')
process.exit(0)
}
resaveImageCollection()
Is the relative .env file location also correct from the folder you build to?
And it looks like you do an update, sure the hook is supposed to be called when updating instead of creating?
Just my shots in the dark. Local API worked amazingly well for me and never had any issues with hooks. Good luck!
Hey @richardvanbergen — when I pass a path directly to dotenv
I have always used a full, absolute path:
dotenv({
path: path.resolve(__dirname, '../../.env'),
});
Try that for your env
issues.
As for hooks, this is strange. Running Payload locally does not have any impact on hook usage. They should work normally as you'd expect. Also, aliasing your server-only code in the Webpack config should have absolutely no effect outside of the browser environment itself. Did you maybe blacklist some dependencies via some other method, which would affect Node as well as Webpack? Seems doubtful but I think your hooks not firing is related to something else.
If you can hook me up with your media-image
collection, I would be happy to troubleshoot!
Hey @jmikrut.
The env thing worked! I wish it would have thrown an error for relative paths instead of just failing silently but hey that's how it goes sometimes.
To my knowledge tsconfig doesn't have anything that should effect but it executes successfully.
I've just had time to do a bit of debugging myself and it seems to be an issue with the beforeChange
and beforeOperation
. Now I'm not 100% sure if these hooks should be called in this context but intuitively it feels like they should. I've stripped down my media-image
collection and removed all the fancy hooks I was adding and just added some simple console.log
hooks. Below is my collection and my script as it stands.
import { CollectionConfig } from 'payload/types'
const MediaImage: CollectionConfig = {
slug: 'media-image',
labels: {
singular: 'Image',
plural: 'Images',
},
access: {
read: () => true,
},
hooks: {
beforeRead: [
(data) => {
console.log('before read!')
console.log(data.doc.id)
return data.doc
},
],
beforeChange: [
({ data }) => {
console.log('before change!')
console.log(data.id)
return data.doc
},
],
beforeOperation: [
({ args, operation }) => {
console.log('before operation!')
if (operation === 'update') {
console.log('before operation update!')
console.log(args)
}
return args
},
],
},
admin: {
useAsTitle: 'internalName',
enableRichTextRelationship: true,
},
fields: [
{
name: 'internalName',
label: 'Internal Name',
type: 'text',
required: true,
},
{
name: 'alt',
label: 'Alternative Text',
type: 'text',
},
],
upload: {
staticDir: 'media/images',
staticURL: '/media/images',
mimeTypes: ['image/*'],
imageSizes: [
{
name: 'desktop',
width: 2000,
height: 2000,
},
{
name: 'mobile',
width: 1000,
height: 1000,
},
],
},
}
export default MediaImage
The script:
import payload from 'payload'
import path from 'path'
import dotenv from 'dotenv'
import { MediaImageField } from '../fields/media'
const result = dotenv.config({
path: path.resolve(__dirname, '../../.env'),
})
console.log(result)
payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
local: true,
})
const resaveImageCollection = async () => {
const mediaImages = await payload.find({
collection: 'media-image',
})
mediaImages.docs.forEach(async (image: MediaImageField) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, ...imageData } = image
await payload.update({
id: image.id,
collection: 'media-image',
data: {
...imageData,
},
})
})
console.log('Seed completed!')
process.exit(0)
}
resaveImageCollection()
The output:
Ah @richardvanbergen I totally know what the issue is.
Your script is async but there is nothing awaiting your forEach
loop responsible for updating. As payload.update
itself is async, your script is exiting before your updates are even fired.
Change your forEach
to this:
await Promise.all(mediaImages.docs.map(async (image: MediaImageField) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, ...imageData } = image
await payload.update({
id: image.id,
collection: 'media-image',
data: {
...imageData,
},
})
}))
Then I bet you'll see some more console logs!
Yes you're correct! It's working for me now. 😃
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.