How to Create a Fulfillment Provider
In this document, you’ll learn how to create a fulfillment provider in the Medusa backend and the methods you must implement in it. If you’re unfamiliar with the Shipping architecture in Medusa, make sure to check out the overview first.
Overview
A fulfillment provider is the shipping provider used to fulfill orders and deliver them to customers. An example of a fulfillment provider is FedEx.
By default, a Medusa Backend has a manual
fulfillment provider which has minimal implementation. It allows you to accept orders and fulfill them manually. However, you can integrate any fulfillment provider into Medusa, and your fulfillment provider can interact with third-party shipping providers.
A fulfillment provider is a service that extends the AbstractFulfillmentService
and implements its methods. So, adding a fulfillment provider is as simple as creating a service file in src/services
.
The file's name is the fulfillment provider's class name as a slug and without the word Service
. For example, if you're creating a MyFulfillmentService
class, the file name is src/services/my-fulfillment.ts
.
Identifier Property
The FulfillmentProvider
entity has 2 properties: identifier
and is_installed
. The identifier
property in the fulfillment provider service is used when the fulfillment provider is added to the database.
The value of this property is also used to reference the fulfillment provider throughout Medusa. For example, it is used to add a fulfillment provider to a region.
constructor
You can use the constructor
of your fulfillment provider to access the different services in Medusa through dependency injection.
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs, you can initialize it in the constructor and use it in other methods in the service. Additionally, if you’re creating your fulfillment provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin, you can access it in the constructor.
Example
Parameters
container
Record<string, unknown>RequiredMedusaContainer
that allows you to access other resources, such as services, in your Medusa backend.config
Record<string, unknown>getFulfillmentOptions
This method is used when retrieving the list of fulfillment options available in a region, particularly by the List Fulfillment Options API Route. For example, if you’re integrating UPS as a fulfillment provider, you might support two fulfillment options: UPS Express Shipping and UPS Access Point. Each of these options can have different data associated with them.
Example
Returns
Promise
Promise<any[]>RequiredThe list of fulfillment options. These options don't have any required format. Later on, these options can be used when creating a shipping option,
such as when using the Create Shipping Option API Route. The chosen fulfillment option, which is one of the
items in the array returned by this method, will be set in the data
object of the shipping option.
Promise
Promise<any[]>Requireddata
object of the shipping option.validateFulfillmentData
This method is called when a shipping method is created. This typically happens when the customer chooses a shipping option during checkout, when a shipping method is created for an order return, or in other similar cases. The shipping option and its data are validated before the shipping method is created.
You can use the provided parameters to validate the chosen shipping option. For example, you can check if the data
object passed as a second parameter includes all data needed to
fulfill the shipment later on.
If any of the data is invalid, you can throw an error. This error will stop Medusa from creating a shipping method and the error message will be returned as a result of the API Route.
Example
class MyFulfillmentService extends AbstractFulfillmentService {
// ...
async validateFulfillmentData(
optionData: Record<string, unknown>,
data: Record<string, unknown>,
cart: Cart
): Promise<Record<string, unknown>> {
if (data.id !== "my-fulfillment") {
throw new Error("invalid data")
}
return {
...data,
}
}
}
Parameters
optionData
ShippingOptionDataRequireddata
FulfillmentProviderDataRequireddata
object passed in the body of the request.The customer's cart details. It may be empty if the shipping method isn't associated with a cart, such as when it's associated with a claim.
Returns
Promise
Promise<Record<string, unknown>>RequiredThe data that will be stored in the data
property of the shipping method to be created.
Make sure the value you return contains everything you need to fulfill the shipment later on. The returned value may also be used to calculate the price of the shipping method
if it doesn't have a set price. It will be passed along to the calculatePrice method.
Promise
Promise<Record<string, unknown>>Requireddata
property of the shipping method to be created.
Make sure the value you return contains everything you need to fulfill the shipment later on. The returned value may also be used to calculate the price of the shipping method
if it doesn't have a set price. It will be passed along to the calculatePrice method.validateOption
Once the admin creates the shipping option, the data of the shipping option will be validated first using this method. This method is called when the Create Shipping Option API Route is used.
Example
For example, you can use this method to ensure that the id
in the data
object is correct:
Parameters
data
ShippingOptionDataRequiredReturns
Promise
Promise<boolean>RequiredWhether the fulfillment option is valid. If the returned value is false, an error is thrown and the shipping option will not be saved.
Promise
Promise<boolean>RequiredcanCalculate
This method is used to determine whether a shipping option is calculated dynamically or flat rate. It is called if the price_type
of the shipping option being created is set to calculated.
Example
Parameters
data
ShippingOptionDataRequireddata
object of the shipping option being created. You can use this data to determine whether the shipping option should be calculated or not.
This is useful if the fulfillment provider you are integrating has both flat rate and dynamically priced fulfillment options.Returns
Promise
Promise<boolean>RequiredIf this method returns true
, that means that the price can be calculated dynamically and the shipping option can have the price_type
set to calculated.
The amount property of the shipping option will then be set to null. The amount will be created later when the shipping method is created on checkout using the calculatePrice method.
If the method returns false
, an error is thrown as it means the selected shipping option is invalid and it can only have the flat_rate
price type.
Promise
Promise<boolean>Requiredtrue
, that means that the price can be calculated dynamically and the shipping option can have the price_type
set to calculated.
The amount property of the shipping option will then be set to null. The amount will be created later when the shipping method is created on checkout using the calculatePrice method.
If the method returns false
, an error is thrown as it means the selected shipping option is invalid and it can only have the flat_rate
price type.calculatePrice
This method is used in different places, including:
- When the shipping options for a cart are retrieved during checkout. If a shipping option has their
price_type
set to calculated, this method is used to set the amount of the returned shipping option. - When a shipping method is created. If the shipping option associated with the method has their
price_type
set tocalculated
, this method is used to set theprice
attribute of the shipping method in the database. - When the cart's totals are calculated.
Example
An example of calculating the price based on some custom logic:
If your fulfillment provider does not provide any dynamically calculated rates you can return any static value or throw an error. For example:
Parameters
optionData
ShippingOptionDataRequireddata
object of the selected shipping option.data
FulfillmentProviderDataRequireddata
object that is different based on the context it's used in:
- If the price is being calculated for the list of shipping options available for a cart, it's the
data
object of the shipping option. - If the price is being calculated when the shipping method is being created, it's the data returned by the validateFulfillmentData method used during the shipping method creation.
- If the price is being calculated while calculating the cart's totals, it will be the data object of the cart's shipping method.
Either the Cart or the Order object.
Returns
Promise
Promise<number>RequiredUsed to set the price of the shipping method or option, based on the context the method is used in.
Promise
Promise<number>RequiredcreateFulfillment
This method is used when a fulfillment is created for an order, a claim, or a swap.
Example
Here is a basic implementation of createFulfillment
for a fulfillment provider that does not interact with any third-party provider to create the fulfillment:
Parameters
data
ShippingMethodDataRequireddata
object of the shipping method associated with the resource, such as the order.
You can use it to access the data specific to the shipping option. This is based on your implementation of previous methods.The line items in the order to be fulfilled. The admin can choose all or some of the items to fulfill.
The details of the created resource, which is either an order, a claim, or a swap:
- If the resource the fulfillment is being created for is a claim, the
is_claim
property in the object will be true
.
- If the resource the fulfillment is being created for is a swap, the
is_swap
property in the object will be true
.
- Otherwise, the resource is an order.
- If the resource the fulfillment is being created for is a claim, the
is_claim
property in the object will betrue
. - If the resource the fulfillment is being created for is a swap, the
is_swap
property in the object will betrue
. - Otherwise, the resource is an order.
The fulfillment being created.
Returns
Promise
Promise<FulfillmentProviderData>RequiredThe data that will be stored in the data
attribute of the created fulfillment.
Promise
Promise<FulfillmentProviderData>Requireddata
attribute of the created fulfillment.cancelFulfillment
This method is called when a fulfillment is cancelled by the admin. This fulfillment can be for an order, a claim, or a swap.
Example
This is the basic implementation of the method for a fulfillment provider that doesn't interact with a third-party provider to cancel the fulfillment:
Parameters
fulfillment
FulfillmentProviderDataRequireddata
attribute of the fulfillment being canceledReturns
Promise
Promise<any>RequiredThe method isn't expected to return any specific data.
Promise
Promise<any>RequiredcreateReturn
Fulfillment providers can also be used to return products. A shipping option can be used for returns if the is_return
property is true or if an admin creates a Return Shipping Option from the settings.
This method is used when the admin creates a return request for an order,
creates a swap for an order, or when the
customer creates a return of their order. The fulfillment is created automatically for the order return.
Example
This is the basic implementation of the method for a fulfillment provider that does not contact with a third-party provider to fulfill the return:
Parameters
returnOrder
CreateReturnTypeRequiredReturns
Promise
Promise<Record<string, unknown>>RequiredUsed to set the value of the shipping_data
attribute of the return being created.
Promise
Promise<Record<string, unknown>>Requiredshipping_data
attribute of the return being created.getFulfillmentDocuments
This method is used to retrieve any documents associated with a fulfillment. This method isn't used by default in the backend, but you can use it for custom use cases such as allowing admins to download these documents.
Example
Parameters
data
FulfillmentProviderDataRequireddata
attribute of the fulfillment that you're retrieving the documents for.Returns
Promise
Promise<any>RequiredThere are no restrictions on the returned response. If your fulfillment provider doesn't provide this functionality, you can leave the method empty or through an error.
Promise
Promise<any>RequiredgetReturnDocuments
This method is used to retrieve any documents associated with a return. This method isn't used by default in the backend, but you can use it for custom use cases such as allowing admins to download these documents.
Example
Parameters
data
Record<string, unknown>RequiredReturns
Promise
Promise<any>RequiredThere are no restrictions on the returned response. If your fulfillment provider doesn't provide this functionality, you can leave the method empty or through an error.
Promise
Promise<any>RequiredgetShipmentDocuments
This method is used to retrieve any documents associated with a shipment. This method isn't used by default in the backend, but you can use it for custom use cases such as allowing admins to download these documents.
Example
Parameters
data
Record<string, unknown>Requireddata
attribute of the shipment that you're retrieving the documents for.Returns
Promise
Promise<any>RequiredThere are no restrictions on the returned response. If your fulfillment provider doesn't provide this functionality, you can leave the method empty or through an error.
Promise
Promise<any>RequiredretrieveDocuments
This method is used to retrieve any documents associated with an order and its fulfillments. This method isn't used by default in the backend, but you can use it for custom use cases such as allowing admins to download these documents.
Example
Parameters
fulfillmentData
Record<string, unknown>Requireddata
attribute of the order's fulfillment.documentType
"label" | "invoice"RequiredReturns
Promise
Promise<any>RequiredThere are no restrictions on the returned response. If your fulfillment provider doesn't provide this functionality, you can leave the method empty or through an error.
Promise
Promise<any>RequiredTest Implementation
If you created your fulfillment provider in a plugin, refer to this guide on how to test plugins.
After finishing your fulfillment provider implementation:
1. Run the build
command in the root of your Medusa backend:
2. Start the backend with the develop
command:
3. Enable your fulfillment provider in one or more regions. You can do that either using the Admin APIs or the Medusa Admin.
4. To test out your fulfillment provider implementation, create a cart and complete an order. You can do that either using the Next.js starter or using Medusa's APIs and clients.