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// other properties...14  protected logger_: Logger15  protected options_: Options16  // assuming you're initializing a client17  protected client18
19  constructor(20    { logger }: InjectedDependencies,21    options: Options22  ) {23    super()24
25    this.logger_ = logger26    this.options_ = options27
28    // TODO initialize your client29  }30}31
32export 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 a list of fulfillment options that this provider supports. Admin users will then choose from these options when they're creating a shipping option. The chosen fulfillment option's object is then stored within the created shipping option's data property. The data property is useful to store data relevant for the third-party provider to later process the fulfillment.

This method is useful if your third-party provider allows you to retrieve support options, carriers, or services from an API. You can then retrieve those and return then in the method, allowing the admin user to choose from the services provided by the third-party provider.

Example

Code
1// other imports...2import { FulfillmentOption } from "@medusajs/framework/types"3
4class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {5  // ...6  async getFulfillmentOptions(): Promise<FulfillmentOption[]> {7    // assuming you have a client8    const services = await this.client.getServices()9
10    return services.map((service) => ({11      id: service.service_id,12      name: service.name,13      service_code: service.code,14      // can add other relevant data for the provider to later process the shipping option.15    }))16  }17}

Returns

PromisePromise<FulfillmentOption[]>
The list of fulfillment options. Each object in the array should have an id property unique to an item, and a name property that's used to display the option in the admin.

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.
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 validates whether a shippin option's price can be calculated during checkout. It's executed when the admin user creates a shipping option of type calculated. If this method returns false, an error is thrown as the shipping option's price can't be calculated.

You can perform the checking using the third-party provider if applicable. The data parameter will hold the shipping option's data property, which includes the data of a fulfillment option returned by getFulfillmentOptions.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async canCalculate(data: CreateShippingOptionDTO): Promise<boolean> {4    // assuming you have a client5    return await this.client.hasRates(data.id)6  }7}

Parameters

The data property of the shipping option.

Returns

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

calculatePrice#

This method calculates the price of a shipping method when it's created or its cart is refreshed.

In this method, you can send a request to your third-party provider to retrieve the prices. The first parameters holds the data property of the shipping method's shipping option, which has fulfillment object data returned by getFulfillmentOptions.

The second parameter holds the data property of the shipping method, which has data returned by validateFulfillmentData. It can also hold custom data passed from the frontend during checkout.

So, using both of these data, assuming you're storing in them data related to the third-party service, you can retrieve the calculated price of the shipping method.

Example

Code
1import { CalculateShippingOptionPriceDTO } from "@medusajs/framework/types"2class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {3  // ...4  async calculatePrice(5    optionData: CalculateShippingOptionPriceDTO["optionData"],6    data: CalculateShippingOptionPriceDTO["data"],7    context: CalculateShippingOptionPriceDTO["context"]8  ): Promise<CalculatedShippingOptionPrice> {9    // assuming the client can calculate the price using10    // the third-party service11    const price = await this.client.calculate(data)12    return {13      calculated_amount: price,14      // Update this boolean value based on your logic15      is_calculated_price_tax_inclusive: true,16    }17  }18}

Parameters

optionDataRecord<string, unknown>
The data property of a shipping option.
dataRecord<string, unknown>
The shipping method's data property with custom data passed from the frontend.
contextCartPropsForFulfillment & object
The context details, such as the cart details.

Returns

The calculated price's details.

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, such as the ID in the third-party provider.

You can also use this method to perform an action with the third-party fulfillment service since a fulfillment is created, such as purchase a label.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async createFulfillment(4    data: any,5    items: any,6    order: any,7    fulfillment: any8  ): Promise<CreateFulfillmentResult> {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        ...(fulfillment.data as object || {}),19        ...externalData20      }21    }22  }23}

Parameters

dataRecord<string, unknown>
The data property of the shipping method this fulfillment is created for.
itemsPartial<Omit<FulfillmentItemDTO, "fulfillment">>[]
The items in the fulfillment.
orderundefined | Partial<FulfillmentOrderDTO>
The order this fulfillment is created for.
fulfillmentPartial<Omit<FulfillmentDTO, "data" | "items" | "provider_id">>
The fulfillment's details.

Returns

PromisePromise<CreateFulfillmentResult>
An object whose data property is stored 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(data: Record<string, unknown>): Promise<any> {4    // assuming the client cancels a fulfillment5    // in the third-party service6    const { external_id } = data as {7      external_id: string8    }9    await this.client.cancel(external_id)10  }11}

Parameters

dataRecord<string, unknown>
The fulfillment's data property.

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. For example, you can store an ID for the fulfillment in the third-party service.

Use this method to perform actions necessary in the third-party fulfillment service, such as purchasing a label for the return fulfillment.

Example

Code
1class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {2  // ...3  async createReturnFulfillment(fulfillment: Record<string, unknown>): Promise<CreateFulfillmentResult> {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 as object || {}),13        ...externalData14      }15    }16  }17}

Parameters

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

Returns

PromisePromise<CreateFulfillmentResult>
An object containing data which is stored in the fulfillment's data property and labels array which is used to create FulfillmentLabels.

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?