Skip to content

Conversation

@jhoward1994
Copy link
Contributor

@jhoward1994 jhoward1994 commented Jun 25, 2025

What does it do?

Implements the client.media.upload() method in the Strapi JavaScript Client. This method allows developers to upload a new media file directly to the Strapi Media Library by sending a POST request to the /api/upload/files endpoint.

Why is it needed?

Previously, uploading media files to Strapi required developers to manually construct and send REST API calls.

How to test it?

  • Client installed, initialized, and authenticated
  1. Initialize the client in your app.
  2. Call client.media.upload
  3. Verify:
    • A successful response structure as defined by FileResponse
    • File appears in Strapi Media Library
    • API returns expected metadata

Related issue(s)/PR(s)

DX-2040

@jhoward1994 jhoward1994 changed the title Feat/file-upload Upload New Media File Jun 25, 2025
@jhoward1994 jhoward1994 marked this pull request as ready for review June 26, 2025 13:28
@jhoward1994 jhoward1994 self-assigned this Jun 26, 2025
@jhoward1994 jhoward1994 added source: client-files Source is the files manager pr: feature New or updates to features labels Jun 26, 2025
Copy link
Contributor

@innerdvations innerdvations left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just one big question to resolve

const mimetype = options?.mimetype || 'application/octet-stream';

const blob = new Blob([file], { type: mimetype });
formData.append('files', blob, filename);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 const blob = new Blob([new Uint8Array(file)], { type: mimetype }); 

Fixes type issue.

However, I'm not sure it will be necessary with my comment above on the method signature

Comment on lines 277 to 282
async upload(file: Blob, fileInfo?: FileUpdateData): Promise<MediaUploadResponse>;
async upload(file: Buffer, options?: FileUploadOptions): Promise<MediaUploadResponse>;
async upload(
file: Blob | Buffer,
optionsOrFileInfo?: FileUploadOptions | FileUpdateData
): Promise<MediaUploadResponse> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned about properly typing this without it being confusing. What do you think about something like:

export interface BlobUploadOptions { fileInfo?: FileUpdateData; } export interface BufferUploadOptions { filename: string; mimetype: string; fileInfo?: FileUpdateData; } // ...  async upload(file: Blob, options?: BlobUploadOptions): Promise<MediaUploadResponse>; async upload(file: Buffer, options?: BufferUploadOptions): Promise<MediaUploadResponse>; async upload( file: Blob | Buffer, options?: BlobUploadOptions | BufferUploadOptions ): Promise<MediaUploadResponse> { // some type checking to ensure if a Blob is passed in, BlobUploadOptions are passed in, etc // could then split out if(isBuffer) uploadBuffer(file, options); // buffer specific method if(isBlob) uploadBlob(file, options); // blob specific method }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be overcomplicating it though; I just would try to avoid having different options interfaces for the two methods, and instead just ensure that filename+metadata are included if required.

It could actually be a lot simpler than I just wrote above, it could be like

 if(isBuffer && !hasFilenameAndMetadata) throw new Error("Filename and metadata are required when uploading Buffer data");

Now that I think about it, that could work better. What do you think?

Copy link
Contributor

@innerdvations innerdvations left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine now, just one last comment for you to take a look at and then I think we can merge.

Comment on lines 297 to 299

return this.uploadBuffer(file, options as BufferUploadOptions, client, url);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 if (file instanceof Buffer && typeof Buffer !== 'undefined' && Buffer.isBuffer(file)) { if (!options || !('filename' in options) || !('mimetype' in options)) { throw new Error('Filename and mimetype are required when uploading Buffer data'); } return this.uploadBuffer(file, options, client, url); } else if (file instanceof Blob) { return this.uploadBlob(file, options, client, url); } else { console.warn('Could not determine type of file; attempting upload as Blob'); return this.uploadBlob(file as unknown as Blob, options as BlobUploadOptions, client, url); }

Haven't tested it, but maybe something like this just to be sure and help with debugging

@jhoward1994 jhoward1994 requested a review from innerdvations July 1, 2025 10:02
Copy link
Contributor

@innerdvations innerdvations left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Tested and LGTM!

Comment on lines +255 to +286
* // Upload with a File object (browser)
* const fileInput = document.querySelector('input[type="file"]');
* const file = fileInput.files[0];
* const result = await filesManager.upload(file, {
* fileInfo: { alternativeText: 'A user uploaded image' }
* });
*
* // Upload with a Blob
* import { blobFrom } from 'node-fetch';
* const file = await blobFrom('./1.png', 'image/png');
* const result = await filesManager.upload(file, {
* fileInfo: { alternativeText: 'An example image' }
* });
*
* // Upload with a Buffer (Node.js) - filename and mimetype are required
* import fs from 'fs';
* const buffer = fs.readFileSync('./image.png');
* const result = await filesManager.upload(buffer, {
* filename: 'image.png',
* mimetype: 'image/png',
* fileInfo: { alternativeText: 'An example image' }
* });
*
* console.log(result); // Upload response with file details
* ```
*/
async upload(file: Blob, options?: BlobUploadOptions): Promise<MediaUploadResponse>;
async upload(file: Buffer, options: BufferUploadOptions): Promise<MediaUploadResponse>;
async upload(
file: Blob | Buffer,
options?: BlobUploadOptions | BufferUploadOptions
): Promise<MediaUploadResponse> {
Copy link
Contributor

@innerdvations innerdvations Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Bassel17 and @maccomaccomaccomacco

Could you add another voice to this method signature in particular? Jamie and I think we've got it, but since changing this would be a breaking change later I want to make sure we're not missing something.

Usage would be those three uses written in the header comment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine, even if we add new stuff to it later it will just be additions and won't be breaking changes, like uploading maybe multiple files or something like that

@jhoward1994 jhoward1994 merged commit 60a0117 into main Jul 3, 2025
7 checks passed
@jhoward1994 jhoward1994 deleted the feat/file-upload branch July 3, 2025 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr: feature New or updates to features source: client-files Source is the files manager

3 participants