How can I post an image from local directory to the Strapi Media Library?

System Information
  • Strapi Version: 4.8.2
  • Operating System: Ubuntu 18.04
  • Database: sqlite
  • Node Version: v16.16.0
  • NPM Version: 8.11.0
  • Yarn Version: 1.22.19

Using Strapi lifecycle hooks, I’m creating a .jpg file when a new post is created. I wish to upload this jpg file to the Strapi media library within the same lifecycle.

I’ve tried:

      let form = new FormData();
      form.append('files', fs.createReadStream("./poster.jpg"));
      const response = await fetch('http://localhost:1337/api/upload', {
        method: 'post',
        body: form,
      });

but nothing happens in the console. I tried posting the same image using Postman with only the “files” field and it works Postman:

Is poster.jpg in the same directory as the file that contains the sample code?

Explaining what you’re trying to achieve will help curate an answer - for example, is this running in a browser and you’re using a file input field, or is this running on another Node.js server and the poster.jpg image is on that server?

Thanks for helping me explain better, Clark. This is going in src/api/card/content-types/card/lifecycles.js, within afterUpdate lifecycle hook. I’m automating poster creation using node-canvas, which outputs a poster.jpg file in the Strapi project root folder. Now I want to upload and associate the poster.jpg with event.params.data.PosterUrl. Here’s the full code:

// lifecycles.js
const { FormData } = require("formdata-node");
const fetch = require("node-fetch");

const api_url = "http://127.0.0.1:1337";
module.exports = {
  async afterUpdate(event) {
    let dataSource = event.params.data;
    if (
      dataSource.Paragraph &&
      dataSource.Paragraph?.length > 0
    ) {
      // get the image url associated with post:
      const imageResource = await strapi.entityService.findOne(
        "plugin::upload.file",
        `${dataSource.Image}`,
        {
          fields: ["url"],
        }
      );
      // convert the relative path as an absolute path to the ImageUrl field of the card content-type:
      const ImageUrl = api_url + JSON.parse(JSON.stringify(imageResource)).url;
      dataSource.ImageUrl = ImageUrl;

      // create a canvas
      const { loadImage, createCanvas } = require("canvas");
      const fs = require("fs");

      // Dimensions for the image
      const width = 378;
      const height = 662.438;

      // Instantiate the canvas object
      const canvas = createCanvas(width, height);
      const ctx = canvas.getContext("2d");

      // Fill the rectangle with white
      ctx.fillStyle = "#a14124";
      ctx.fillRect(0, 0, width, height);
      ctx.fillText("hello world", width, height);

      // load the background image
      loadImage(dataSource.ImageUrl)
        .then((image) => {
          // draw the background image on the canvas
          ctx.drawImage(image, 0, 0, width, height);

          // add Headline to the image
          ctx.fillStyle = "white";
          ctx.font = "bold 48px sans-serif";
          ctx.textAlign = "center";
          ctx.fillText(dataSource.Headline, width / 2, height / 3);

          // Add Paragraph to image
          ctx.fillStyle = "white";
          ctx.font = "bold 48px sans-serif";
          ctx.textAlign = "center";
          ctx.fillText(dataSource.Paragraph, width / 2, height / 2);

          // place the canvas to a JPEG buffer
          const buffer = canvas.toBuffer("image/jpeg");

          // save the buffer as a file
          fs.writeFileSync("./poster.jpg", buffer);
        })
        .catch((err) => {
          console.error(err);
        });

        // Create a form with the newly created image
        const form = new FormData();
        form.append('files', fs.createReadStream("./hello_world.jpg"));
        // post the image to the media library
        const response = await fetch('http://localhost:1337/api/upload', {
          method: 'post',
          body: form,
        });
    }
  },
};

For anyone wondering how I spent my last 10 hours, here’s a working solution:

        const mime = require('mime-types'); //used to detect file's mime type
        const fileName = 'poster.jpg';
        const filePath = `./${fileName}`
        const stats = fs.statSync(filePath)
        await strapi.plugins.upload.services.upload.upload({
          data:{}, //mandatory declare the data(can be empty), otherwise it will give you an undefined error.
          files: {
          path: './poster.jpg',
          name: 'poster.jpg',
          type: mime.lookup(filePath), // mime type of the file
          size: stats.size,
        },
      });
1 Like

I haven’t got time right now to go through the code line by line but a couple of things stand out to me.

  1. const fileName = 'poster.jpg'; this should be a unique filename. Consider using UUID, adding a randomly generated hash, or timestamp (+new Date()) to create a random file name.
  2. If you don’t change the filename, are you worried about synchronous requests picking up on the same file by other users?
  3. You should probably store the files in a sub-folder, not the root folder.
  4. Once it’s uploaded into Strapi’s media folder, you should probably delete the image (poster.jpg).
  5. Your image size is relatively small, any reason why it’s not bigger (> 2,000 px)? You can create a large image now and create smaller versions using Strapi’s Media Library breakpoint settings.
    a) This will help later if you need to create larger versions of the images you have already generated in the past.

I hope this helps!

1 Like

@dallasclark I apologize for the extremely late reply. But thank you for sharing your thoughts. As someone who learned coding through YouTube and boot camps, I really appreciate you helping me understand best practices. This makes me think, there should be a platform where novice coders can get feedback for their projects (that is not Stackoverflow).

1 Like

Any time, I was once a beginner :grin: