How to Create a Fulfillment Provider Module

In this document, you’ll learn how to create a fulfillment provider module and the methods you must implement in its main service.


1. Create Module Directory#

Start by creating a new directory for your module. For example, src/modules/my-fulfillment.


2. Create the Fulfillment Provider Service#

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

src/modules/my-fulfillment/service.ts
1import { AbstractFulfillmentProviderService } from "@medusajs/framework/utils"2
3class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {4  // TODO implement methods5}6
7export default MyFulfillmentProviderService

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.

NoteA module's options are passed when you register it in the Medusa application.

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

Example

src/modules/my-fulfillment/service.ts
1import { AbstractFulfillmentProviderService } from "@medusajs/framework/utils"2import { Logger } from "@medusajs/framework/types"3
4type InjectedDependencies = {5  logger: Logger6}7
8type Options = {9  apiKey: string10}11
12class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {13  protected logger_: Logger14  protected options_: Options15  // assuming you're initializing a client16  protected client17
18  constructor(19    { logger }: InjectedDependencies,20    options: Options21  ) {22    super()23
24    this.logger_ = logger25    this.options_ = options26  }27}28
29export default MyFulfillmentProviderService

identifier#

Each fulfillment provider has a unique identifier defined in its class. The provider's ID will be stored as fp_{identifier}_{id}, where {id} is the provider's id property in the medusa-config.ts.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  static identifier = "my-fulfillment"3
4  // ...5}

getFulfillmentOptions#

This method retrieves the shipping options this fulfillment provider supports.

Example

Code
1// other imports...2import { FulfillmentOption } from "@medusajs/framework/types"3
4class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {5  // ...6  async getFulfillmentOptions(): Promise<FulfillmentOption[]> {7    return [8      {9        id: "express"10      },11      {12        id: "return-express",13        is_return: true14      }15    ]16  }17}

Returns

PromisePromise<FulfillmentOption[]>
The list of fulfillment options.

validateFulfillmentData#

This method validates the data property of a shipping method and returns it. The returned data is stored in the shipping method's data property.

Your fulfillment provider can use the data property to store additional information useful for handling the fulfillment later. For example, you may store an ID from the third-party fulfillment system.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async validateFulfillmentData(4    optionData: any,5    data: any,6    context: any7  ): Promise<any> {8    // assuming your client retrieves an ID from the9    // third-party service10    const externalId = await this.client.getId()11
12    return {13      ...data,14      externalId15    }16  }17}

Parameters

optionDataRecord<string, unknown>
The data property of the shipping option.
dataRecord<string, unknown>
The data property of the shipping method.
contextRecord<string, unknown>
Context details, such as context of the cart or customer.

Returns

PromisePromise<any>
the data to store in the data property of the shipping method.

validateOption#

This method validates the data property of a shipping option when it's created.

The data property can hold useful information that's later added to the data attribute of shipping methods created from this option.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async validateOption(data: any): Promise<boolean> {4    return data.external_id !== undefined5  }6}

Parameters

dataRecord<string, unknown>
The data to validate.

Returns

PromisePromise<boolean>
Whether the data is valid.

canCalculate#

This method indicates whether a shippin option's price is calculated during checkout or is fixed.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async canCalculate(data: any): Promise<boolean> {4    return data.custom_type !== "fixed"5  }6}

Parameters

dataRecord<string, unknown>
The data property of the shipping option.

Returns

PromisePromise<boolean>
Whether the price is calculated for the shipping option.

calculatePrice#

This method calculates the price of a shipping option, or a shipping method when it's created.

The Medusa application uses the canCalculate method first to check whether the shipping option's price is calculated. If it returns true, Medusa uses this method to retrieve the calculated price.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async calculatePrice(optionData: any, data: any, context: any): Promise<number> {4    // assuming the client can calculate the price using5    // the third-party service6    const price = await this.client.calculate(data)7    return price8  }9}

Parameters

optionDataRecord<string, unknown>
Shipping option data from the provider, the data property of a shipping option.
dataRecord<string, unknown>
Additional data passed when the price is calculated.
contextCartDTO & object & Record<string, unknown>
The context details, such as the cart or customer.

Returns

The calculated price

createFulfillment#

This method is used when a fulfillment is created. If the method returns in the object a data property, it's stored in the fulfillment's data property.

The data property is useful when handling the fulfillment later, as you can access information useful for your integration.

You can also use this method to perform an action with the third-party fulfillment service.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async createFulfillment(4    data: any,5    items: any,6    order: any,7    fulfillment: any8  ): Promise<any> {9    // assuming the client creates a fulfillment10    // in the third-party service11    const externalData = await this.client.create(12      fulfillment,13      items14    )15
16    return {17      data: {18        ...data,19        ...externalData20      }21    }22  }23}

Parameters

dataobject
The data property of the shipping method this fulfillment is created for.
itemsobject[]
The items in the fulfillment.
orderundefined | object
The order this fulfillment is created for.
fulfillmentRecord<string, unknown>
The fulfillment's details.

Returns

PromisePromise<any>
The data to store in the fulfillment's data property.

cancelFulfillment#

This method is used when a fulfillment is canceled. Use it to perform operations with the third-party fulfillment service.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async cancelFulfillment(fulfillment: any): Promise<any> {4    // assuming the client cancels a fulfillment5    // in the third-party service6    await this.client.cancel(fulfillment.id)7  }8}

Parameters

fulfillmentRecord<string, unknown>
The fulfillment's details.

Returns

PromisePromise<any>
This method is used when a fulfillment is canceled. Use it to perform operations with the third-party fulfillment service.

getFulfillmentDocuments#

This method retrieves the documents of a fulfillment.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async getFulfillmentDocuments(data: any): Promise<never[]> {4    // assuming the client retrieves documents5    // from a third-party service6    return await this.client.documents(data)7  }8}

Parameters

dataRecord<string, unknown>
The data property of the fulfillment.

Returns

PromisePromise<never[]>
The fulfillment's documents.

createReturnFulfillment#

This method is used when a fulfillment is created for a return. If the method returns in the object a data property, it's stored in the fulfillment's data property.

The data property is useful when handling the fulfillment later, as you can access information useful for your integration.

Use this method to perform actions necessary in the third-party fulfillment service.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async createReturnFulfillment(fulfillment: any): Promise<any> {4    // assuming the client creates a fulfillment for a return5    // in the third-party service6    const externalData = await this.client.createReturn(7      fulfillment8    )9
10    return {11      data: {12        ...fulfillment.data,13        ...externalData14      }15    }16  }17}

Parameters

fulfillmentRecord<string, unknown>
The fulfillment's details.

Returns

PromisePromise<any>
The data to store in the fulfillment's data property.

getReturnDocuments#

This method retrieves documents for a return's fulfillment.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async getReturnDocuments(data: any): Promise<never[]> {4    // assuming the client retrieves documents5    // from a third-party service6    return await this.client.documents(data)7  }8}

Parameters

dataRecord<string, unknown>
The data property of the fulfillment.

Returns

PromisePromise<never[]>
The fulfillment's documents.

getShipmentDocuments#

This method retrieves the documents for a shipment.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async getShipmentDocuments(data: any): Promise<never[]> {4    // assuming the client retrieves documents5    // from a third-party service6    return await this.client.documents(data)7  }8}

Parameters

dataRecord<string, unknown>
The data property of the shipmnet.

Returns

PromisePromise<never[]>
The shipment's documents.

retrieveDocuments#

This method retrieves the documents of a fulfillment of a certain type.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async retrieveDocuments(4    fulfillmentData: any,5    documentType: any6  ): Promise<void> {7    // assuming the client retrieves documents8    // from a third-party service9    return await this.client.documents(10      fulfillmentData,11      documentType12    )13  }14}

Parameters

fulfillmentDataRecord<string, unknown>
The data property of the fulfillment.
documentTypestring
The document's type. For example, invoice.

Returns

PromisePromise<void>
The fulfillment's documents.

3. Create Module Definition File#

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

src/modules/my-fulfillment/index.ts
1import MyFulfillmentProviderService from "./service"2import { 3  ModuleProvider, 4  Modules5} from "@medusajs/framework/utils"6
7export default ModuleProvider(Modules.FULFILLMENT, {8  services: [MyFulfillmentProviderService],9})

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


4. Use Module#

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

medusa-config.ts
1module.exports = defineConfig({2  // ...3  modules: [4    {5      resolve: "@medusajs/medusa/fulfillment",6      options: {7        providers: [8          // default provider9          {10            resolve: "@medusajs/medusa/fulfillment-manual",11            id: "manual",12          },13          {14            resolve: "./src/modules/my-fulfillment",15            id: "my-fulfillment",16            options: {17              // provider options...18            },19          },20        ],21      },22    },23  ]24})

5. Test it Out#

Before you use your fulfillment provider, in the Medusa Admin:

  1. Add the fulfillment provider to a location.
  2. Add in the location a delivery shipping option that uses the provider.

Then, place an order, choosing the shipping option you created during checkout, and create a fulfillment in the Medusa Admin. The fulfillment is created using your provider.

Was this page helpful?