Can't successfully upload a file to an existing entry from Node JS

System Information
  • Strapi Version: 3.3.4
  • Operating System: mac os Catalina
  • Database: postgres
  • Node Version: 14.15.1
  • NPM Version: 6.14.9
  • Yarn Version: 1.22.10

Describe the bug

Hello,
When trying to upload a file from node js (i need to bulk upload images for thousands of entries), i get an error
‘(node:38011) UnhandledPromiseRejectionWarning: TypeError: Cannot destructure property ‘fileInfo’ of ‘data’ as it is undefined.
at Object.upload (/Users/jbm/Dev/whatgear/backend/node_modules/strapi-plugin-upload/services/Upload.js:128:13)’
A clear and concise description of what the bug is.

Note that the documentation is very confusing and only show code for upload via frontend:
https://strapi.io/documentation/v3.x/plugins/upload.html#upload-files-related-to-an-entry

Steps to reproduce the behavior

So i searched for hours on end and only found few resources about uploading via node js, most f them being obsolete / for old api, etc…

I ended up with this code:

(Readir from a local folder containing the pictures, i did not include the rest of the code (iterating through filenames to match the related entity id, etc…)

const form = new FormData();
                
const fileStream = fs.createReadStream('./path/to/myfile.jpg')

form.append('files', fileStream, 'myfile.jpg');

form.append('data', JSON.stringify({
    ref: 'collection',
    refId: entity.id,
    field: 'pictures',
}));

await strapi.plugins['upload'].services.upload.upload(form, {
    headers: form.getHeaders()
});

            `

It throw me this error
‘(node:38011) UnhandledPromiseRejectionWarning: TypeError: Cannot destructure property ‘fileInfo’ of ‘data’ as it is undefined.
at Object.upload (/Users/jbm/Dev/project/backend/node_modules/strapi-plugin-upload/services/Upload.js:128:13)’
A clear and concise description of what the bug is.

Expected behavior

Should upload file for related entry


Can you try this?
form.append('files', fileStream, 'myfile.jpg');
form.append('ref', 'collection')
form.append('refId', entity.id)
form.append('field', 'pictures')
2 Likes

Hi,

Yes i tried that exact way aswel and i have the same error:

UnhandledPromiseRejectionWarning: TypeError: Cannot destructure property ‘fileInfo’ of ‘data’ as it is undefined.
at Object.upload (/Users/jbm/Dev/project/backend/node_modules/strapi-plugin-upload/services/Upload.js:129:13)

It looks like there is an issue on how the upload function destructures the form data (node_modules/strapi-plugin-upload/controllers/services/Upload.js, Line 127)

when trying to console log it’s indeed undefined
async upload({ data, files }, { user } = {}) {

console.log('LOG DATAS', data, 'LOG FILES', files)

const { fileInfo, ...metas } = data;

etc…
}

Both are undefined.

Oh, my fault, I just noticed that you use the upload service, I was thinking you tried to upload them with Rest API.

You can try to append fileInfo to data, it is an object: {"caption":"new_caption","alternativeText":"alternative text"} that one should be stringified.

Also, you can take a look at the controller which uses the service:

I tried to append fileInfo, i have the same error, also i cant really understand what to do with the file you suggested me to watch, but thanks for your help. Im still at the same point right now.

It has been two days of trying to use strapi upload, if i had a suggestion to make is to enhance the documentation on this matter, because it’s very poor and confusing.

Maybe you could guide me on how to do it with rest api?
Still the issue with the service may be considered a bug?

It’s not a bug, it’s just not meant right now to be used directly.

Take a look at this discussion:

1 Like

@jbm I had the exact same problem I was able to implement this using the upload service using the following code

const fileStat = fs.statSync(filepath);
const attachment = await strapi.plugins.upload.services.upload.upload({
	data: {
		refId: result.id,
		ref: 'my-collection',
		field: 'attachments',
	},
	files: {
		path: filepath,
		name: `filename.pdf`,
		type: 'application/pdf', // mime type
		size: fileStat.size,
	},
});
9 Likes

You are a genius, thanks !!!

Thanks for your help Sunnyson, Periabytes have the solution, it should be added to the doc!

1 Like

Thanks! Genius! Awesome works.

May i ask where did you find the documentation for this ??

I didn’t find it in the documentation, I just tinkered with the upload plugin’s services and reading the source code, specifically the Upload.js file inside node_modules/strapi-plugin-upload/services/Upload.js

It looks good but what if I want to upload a file from an online url ? (like a png or jpg) If I set the file’s url as filepath your solution don’t work…

I think You must find a way to “download” the file from the URL then attach it the same way after you’ve downloaded it.

1 Like

Ok thanks!

Hello! Did you figure out a solution to uploading a file from an online URL?

Hi

I have taken this from a current project and slightly adjusted it for you. Hopefully, I haven’t made any mistakes. Don’t forget the authToken (in the format of Bearer ....).

Let me know if it solves your problem.

const fetch = require("node-fetch"); // is already a strapi dependency
const FormData = require("form-data"); // is already a strapi dependency
const path = require("path");
const { getAbsoluteServerUrl } = require("strapi-utils");

const uploadImage = async ({ url, fieldName, id, authToken, collection }) => {
  const imageRes = await fetch(url);
  if (imageRes.status !== 200) return;

  const filename = path.basename(url);

  const arrayBuffer = await imageRes.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);

  const formData = new FormData();

  formData.append("files", buffer, filename);

  // if adding to an existing entry ( we need the relevant collection, ID, and fieldName)
  if (id && fieldName && collection) {
    formData.append("ref", collection);
    formData.append("refId", id);
    formData.append("field", fieldName);
  }

  const baseUrl = getAbsoluteServerUrl(strapi.config);

  return fetch(`${baseUrl}/upload`, {
    method: "POST",
    body: formData,
    headers: {
      Authorization: authToken,
      ...formData.getHeaders(),
    },
  })
    .then((res) => res.json())
    .catch((e) => console.log("e", e));
};

Thank you @mattf96!

I have been trying to not make an API call to Strapi from within since we have access to all of the Strapi internal plugins but looks like I have no choice. The last part that I’m stuck on is getting the authToken. I’m making this call on a lifecycle hook afterCreate. I guess I can technically login to Strapi from within as well but wasn’t sure if anyone had a better way.