Like what we’re doing? Star us on GitHub!

Executing hooks on existing collections.

richardvanbergen
last year
2 1

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?

  • jmikrut
    Payload Team
    last year

    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?

    6 replies
  • richardvanbergen
    last year

    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()
  • Ontopic
    last year

    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!

  • jmikrut
    Payload Team
    last year

    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!

  • richardvanbergen
    last year

    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:

    image

  • jmikrut
    Payload Team
    last year

    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!

  • richardvanbergen
    last year

    Yes you're correct! It's working for me now. 😃

Open the post
Continue the discussion in GitHub
Can't find what you're looking for?
Get help straight from the Payload team with an Enterprise License.Learn More