Back to Medusa

How to Create a File Module Provider

www/apps/resources/references/file/classes/file.AbstractFileProviderService/page.mdx

2.14.221.4 KB
Original Source

import { TypeList } from "docs-ui"

How to Create a File Module Provider

In this document, you’ll learn how to create a File Module Provider and the methods you must implement in its main service.


Implementation Example

As you implement your File Module Provider, it can be useful to refer to an existing provider and how it's implemeted.

If you need to refer to an existing implementation as an example, check the S3 File Module Provider in the Medusa repository.


Create Module Provider Directory

Start by creating a new directory for your module provider.

If you're creating the module provider in a Medusa application, create it under the src/modules directory. For example, src/modules/my-file.

If you're creating the module provider in a plugin, create it under the src/providers directory. For example, src/providers/my-file.

<Note>

The rest of this guide always uses the src/modules/my-file directory as an example.

</Note>

2. Create the File Module Provider's Service

Create the file src/modules/my-file/service.ts that holds the implementation of the module provider's main service. It must extend the AbstractFileProviderService class imported from @medusajs/framework/utils:

ts
import { AbstractFileProviderService } from "@medusajs/framework/utils"

class MyFileProviderService extends AbstractFileProviderService {
  // TODO implement methods
}

export default MyFileProviderService

constructor

The constructor allows you to access resources from the module's container using the first parameter, and the module's options using the second parameter.

If you're creating a client or establishing a connection with a third-party service, do it in the constructor.

Example

ts
import { Logger } from "@medusajs/framework/types"
import { AbstractFileProviderService } from "@medusajs/framework/utils"

type InjectedDependencies = {
  logger: Logger
}

type Options = {
  apiKey: string
}

class MyFileProviderService extends AbstractFileProviderService {
  protected logger_: Logger
  protected options_: Options
  static identifier = "my-file"
  // assuming you're initializing a client
  protected client

  constructor (
    { logger }: InjectedDependencies,
    options: Options
  ) {
    super()

    this.logger_ = logger
    this.options_ = options

    // assuming you're initializing a client
    this.client = new Client(options)
  }
}

export default MyFileProviderService

identifier

Each file provider has a unique ID used to identify it. The provider's ID will be stored as fs_{identifier}_{id}, where {id} is the provider's id property in the medusa-config.ts.

Example

ts
class MyFileProviderService extends AbstractFileProviderService {
  static identifier = "my-file"
  // ...
}

delete

This method deletes one or more files from the storage. It's used when an admin user deletes a product image, or other custom file deletions.

Example

ts
class MyFileProviderService extends AbstractFileProviderService {
  // ...
  async delete(
    files: FileTypes.ProviderDeleteFileDTO | FileTypes.ProviderDeleteFileDTO[]
  ): Promise<void> {
    // TODO logic to remove the file from storage
    // Use the `file.fileKey` to delete the file, which is the identifier of the file
   // in the provider's storage.
    // for example:
    const fileArray = Array.isArray(files) ? files : [files]
    for (const file of fileArray) {
      this.client.delete(file.fileKey)
    }
  }
}

Parameters

<TypeList types={[{"name":"files","type":"ProviderDeleteFileDTO \| ProviderDeleteFileDTO[]","description":"The details of the files to delete.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"string","description":"The file's key. When uploading a file, the\nreturned key is used here.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="delete"/>

Returns

<TypeList types={[{"name":"Promise","type":"Promise<void>","optional":false,"defaultValue":"","description":"Resolves when the files are deleted.","expandable":false,"children":[]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="delete"/>

getAsBuffer

This method retrieves an uploaded file as a buffer. This is useful when you want to process the entire file in memory or send it as a response.

:::note

This is available starting from Medusa v2.8.0.

:::

Example

ts
class MyFileProviderService extends AbstractFileProviderService {
  // ...
  async getAsBuffer(file: ProviderDeleteFileDTO): Promise<Buffer> {
    // TODO logic to get the file as a buffer
    // Use the `file.fileKey` to get the file, which is the identifier of the file
    // in the provider's storage.
    // for example:
    this.client.getAsBuffer(file.fileKey)
  }
}

Parameters

<TypeList types={[{"name":"fileData","type":"ProviderGetFileDTO","description":"The details of the file to get its buffer.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"string","description":"The file's key allowing you to later\nidentify the file in the third-party\nsystem. For example, the S3 Module Provider\nreturns the file's key in S3, whereas the\nLocal File Module Provider returns the file's\npath.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getAsBuffer"/>

Returns

<TypeList types={[{"name":"Promise","type":"Promise<Buffer<ArrayBufferLike>>","optional":false,"defaultValue":"","description":"The file's buffer.","expandable":false,"children":[],"since":"2.8.0"}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getAsBuffer"/>

getDownloadStream

This method retrieves an uploaded file as a stream. This is useful when streaming a file to clients or you want to process the file in chunks.

:::note

This is available starting from Medusa v2.8.0.

:::

Example

ts
class MyFileProviderService extends AbstractFileProviderService {
  // ...
  async getAsStream(file: ProviderDeleteFileDTO): Promise<Readable> {
   // TODO logic to get the file as a stream
   // Use the `file.fileKey` to get the file, which is the identifier of the file
   // in the provider's storage.
   // for example:
    this.client.getAsStream(file.fileKey)
  }
}

Parameters

<TypeList types={[{"name":"fileData","type":"ProviderGetFileDTO","description":"The details of the file to get its stream.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"string","description":"The file's key allowing you to later\nidentify the file in the third-party\nsystem. For example, the S3 Module Provider\nreturns the file's key in S3, whereas the\nLocal File Module Provider returns the file's\npath.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getDownloadStream"/>

Returns

<TypeList types={[{"name":"Promise","type":"Promise<Readable>","optional":false,"defaultValue":"","description":"The file's stream.","expandable":false,"children":[],"since":"2.8.0"}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getDownloadStream"/>

getPresignedDownloadUrl

This method is used to retrieve a download URL of the file. For some providers, such as S3, a presigned URL indicates a temporary URL to get access to a file.

If your provider doesn’t perform or offer a similar functionality, you can return the URL to download the file.

Example

ts
class MyFileProviderService extends AbstractFileProviderService {
  // ...
  async getPresignedDownloadUrl(
    fileData: ProviderGetFileDTO
  ): Promise<string> {
    // TODO logic to get the presigned URL
    // Use the `file.fileKey` to delete the file, which is the identifier of the file
   // in the provider's storage.
    // for example:
    return this.client.getPresignedUrl(fileData.fileKey)
  }
}

Parameters

<TypeList types={[{"name":"fileData","type":"ProviderGetFileDTO","description":"The details of the file to get its\npresigned URL.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"fileKey","type":"string","description":"The file's key allowing you to later\nidentify the file in the third-party\nsystem. For example, the S3 Module Provider\nreturns the file's key in S3, whereas the\nLocal File Module Provider returns the file's\npath.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getPresignedDownloadUrl"/>

Returns

<TypeList types={[{"name":"Promise","type":"Promise<string>","optional":false,"defaultValue":"","description":"The file's presigned URL.","expandable":false,"children":[{"name":"string","type":"string","optional":false,"defaultValue":"","description":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getPresignedDownloadUrl"/>

getPresignedUploadUrl

<BadgesList badges={[ { "variant": "neutral", "children": "optional" } ]} className="mb-1" />

This method is used to get a presigned upload URL for a file. For some providers, such as S3, a presigned URL indicates a temporary URL to get upload a file.

If your provider doesn’t perform or offer a similar functionality, you don't have to implement this method. Instead, an error is thrown when the method is called.

Example

ts
class MyFileProviderService extends AbstractFileProviderService {
  // ...
  async getPresignedUploadUrl(
    fileData: ProviderGetPresignedUploadUrlDTO
  ): Promise<ProviderFileResultDTO> {
    // TODO logic to get the presigned upload URL
    // for example:
    return this.client.getPresignedUploadUrl(fileData.filename, fileData.mimeType)
  }
}

Parameters

<TypeList types={[{"name":"fileData","type":"ProviderGetPresignedUploadUrlDTO","description":"The details of the file to get a presigned upload URL for.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"filename","type":"string","description":"The filename of the file to get a presigned upload URL for.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"mimeType","type":"string","description":"The mimetype of the file to get a presigned upload URL for.","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"access","type":"FileAccessPermission","description":"The access level of the file to get a presigned upload URL for.","optional":true,"defaultValue":"","expandable":false,"children":[]},{"name":"expiresIn","type":"number","description":"The validity of the presigned upload URL in seconds.","optional":true,"defaultValue":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getPresignedUploadUrl"/>

Returns

<TypeList types={[{"name":"Promise","type":"Promise<ProviderFileResultDTO>","optional":false,"defaultValue":"","description":"The presigned URL and file key to upload the file to","expandable":false,"children":[{"name":"url","type":"string","description":"The file's URL that users or systems\ncan use to access the file.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"key","type":"string","description":"The file's key allowing you to later\nidentify the file in the third-party\nsystem. For example, the S3 Module Provider\nreturns the file's key in S3, whereas the\nLocal File Module Provider returns the file's\npath.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getPresignedUploadUrl"/>

getUploadStream

This method returns a writeable stream to upload a file.

:::note

This is available starting from Medusa v2.8.0.

:::

Example

ts
class MyFileProviderService extends AbstractFileProviderService {
  // ...
  async getUploadStream(fileData: FileTypes.ProviderUploadStreamDTO): Promise<{
    writeStream: Writable
    promise: Promise<FileTypes.ProviderFileResultDTO>
    url: string
    fileKey: string
  }> {
    // TODO logic to get the writeable stream
  }
}

Parameters

<TypeList types={[{"name":"fileData","type":"ProviderUploadStreamDTO","description":"The details of the file to upload.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"filename","type":"string","description":"The filename of the uploaded file","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"mimeType","type":"string","description":"The mimetype of the uploaded file","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"access","type":"FileAccessPermission","description":"The access level of the file. Defaults to private if not passed","optional":true,"defaultValue":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getUploadStream"/>

Returns

<TypeList types={[{"name":"Promise","type":"Promise<object>","optional":false,"defaultValue":"","description":"The writeable stream and upload promise.","expandable":false,"children":[{"name":"writeStream","type":"Writable","description":"","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"promise","type":"Promise<ProviderFileResultDTO>","description":"","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"url","type":"string","description":"The file's URL that users or systems\ncan use to access the file.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"key","type":"string","description":"The file's key allowing you to later\nidentify the file in the third-party\nsystem. For example, the S3 Module Provider\nreturns the file's key in S3, whereas the\nLocal File Module Provider returns the file's\npath.","optional":false,"defaultValue":"","expandable":false,"children":[]}]},{"name":"url","type":"string","description":"","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"fileKey","type":"string","description":"","optional":false,"defaultValue":"","expandable":false,"children":[]}],"since":"2.8.0"}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="getUploadStream"/>

upload

This method uploads a file using your provider's custom logic. In this method, you can upload the file into your provider's storage, and return the uploaded file's details.

This method will be used when uploading product images, CSV files for imports, or other custom file uploads.

Example

ts
class MyFileProviderService extends AbstractFileProviderService {
  // ...
  async upload(
    file: ProviderUploadFileDTO
  ): Promise<ProviderFileResultDTO> {
    // TODO upload file to third-party provider
    // or using custom logic
    // for example:
    this.client.upload(file)

    return {
      url: "some-url.com",
      key: "file-name-or-id"
    }
  }
}

Parameters

<TypeList types={[{"name":"file","type":"ProviderUploadFileDTO","description":"The file to upload.","optional":false,"defaultValue":"","expandable":false,"children":[{"name":"filename","type":"string","description":"The filename of the uploaded file","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"mimeType","type":"string","description":"The mimetype of the uploaded file","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"content","type":"string","description":"The file content as a base64-encoded string","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"access","type":"FileAccessPermission","description":"The access level of the file. Defaults to private if not passed","optional":true,"defaultValue":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="upload"/>

Returns

<TypeList types={[{"name":"Promise","type":"Promise<ProviderFileResultDTO>","optional":false,"defaultValue":"","description":"The uploaded file's details.","expandable":false,"children":[{"name":"url","type":"string","description":"The file's URL that users or systems\ncan use to access the file.","optional":false,"defaultValue":"","expandable":false,"children":[]},{"name":"key","type":"string","description":"The file's key allowing you to later\nidentify the file in the third-party\nsystem. For example, the S3 Module Provider\nreturns the file's key in S3, whereas the\nLocal File Module Provider returns the file's\npath.","optional":false,"defaultValue":"","expandable":false,"children":[]}]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="upload"/>

validateOptions

This method validates the options of the provider set in medusa-config.ts. Implementing this method is optional. It's useful if your provider requires custom validation.

If the options aren't valid, throw an error.

Example

ts
class MyFileProviderService extends AbstractFileProviderService {
  static validateOptions(options: Record<any, any>) {
    if (!options.apiKey) {
      throw new MedusaError(
        MedusaError.Types.INVALID_DATA,
        "API key is required in the provider's options."
      )
    }
  }
}

Parameters

<TypeList types={[{"name":"options","type":"Record<any, any>","description":"The provider's options.","optional":false,"defaultValue":"","expandable":false,"children":[]}]} expandUrl="https://docs.medusajs.com/learn/fundamentals/data-models/manage-relationships#retrieve-records-of-relation" sectionTitle="validateOptions"/>


3. Create Module Provider Definition File

Create the file src/modules/my-file/index.ts with the following content:

ts
import MyFileProviderService from "./service"
import { 
  ModuleProvider, 
  Modules
} from "@medusajs/framework/utils"

export default ModuleProvider(Modules.FILE, {
  services: [MyFileProviderService],
})

This exports the module provider's definition, indicating that the MyFileProviderService is the module provider's service.


4. Use Module Provider

To use your File Module Provider, add it to the providers array of the File Module in medusa-config.ts:

<Note>

The File Module accepts one provider only.

</Note>
ts
module.exports = defineConfig({
  // ...
  modules: [
    {
      resolve: "@medusajs/medusa/file",
      options: {
        providers: [
          // default provider
          {
            resolve: "@medusajs/medusa/file-local",
            id: "local",
          },
          {
            // if module provider is in a plugin, use `plugin-name/providers/my-file`
            resolve: "./src/modules/my-file",
            id: "my-file",
            options: {
              // provider options...
            },
          },
        ],
      },
    },
  ]
})

5. Test it Out

To test out your File Module Provider, use the Medusa Admin or the Upload API route to upload a file.