nShift
nShift carrier rates and shipments
@solteq-excom/medusa-fulfillment-nshift
nShift Checkout fulfillment provider for Medusa v2.
Brings nShift's delivery options into a Medusa storefront: real carrier options, real prices for the customer's address and basket, and a partial shipment created in nShift when the order is fulfilled.
- Dynamic delivery options — the options an admin can attach to a shipping option come straight from your nShift Checkout configuration.
- Calculated pricing — prices are fetched per cart, using the receiver address, package weight/volume and basket value, so nShift price rules (free shipping thresholds, weight bands, per-country pricing) apply.
- Pickup points, time slots and addons — selected at checkout, validated against nShift, and forwarded to the shipment.
- Partial shipments — created on fulfillment, deleted again when the fulfillment is cancelled.
- Fails soft — if nShift is unreachable or does not offer an option for a cart, that option is simply reported as unavailable. Checkout keeps working.
- Token and session handling — OAuth2 tokens are cached and refreshed; one nShift session is reused across all the options of a cart.
Requirements
- Medusa Copy to clipboard
>= 2.13.3 - Node Copy to clipboard
>= 20 - An active nShift Checkout agreement
Installation
123yarn add @solteq-excom/medusa-fulfillment-nshift# ornpm install @solteq-excom/medusa-fulfillment-nshift
nShift account setup
- Log in to the nShift Portal.
- Settings → API Configuration → Clients → Add. Give the client the Public checkout API scope and copy the Client ID and Client Secret (the secret is only shown once).
- Connections — copy the Connection ID you want Medusa to use.
- Configure the Checkout configuration behind that connection with the carriers and delivery options you want to offer.
Configuration
Register the provider on the Fulfillment Module, and register the plugin so its build output is loaded:
1234567891011121314151617181920// medusa-config.tsimport { defineConfig } from "@medusajs/framework/utils"export default defineConfig({plugins: [{resolve: "@solteq-excom/medusa-fulfillment-nshift",options: {},},],modules: [{resolve: "@medusajs/medusa/fulfillment",options: {providers: [{resolve: "@medusajs/medusa/fulfillment-manual",id: "manual",},{
Define the Fulfillment Module once. If it appears twice in Copy to clipboardmodules, the last definition wins and the provider in the earlier one is never registered — which makes every cart that references an nShift shipping option fail.12345678NSHIFT_CLIENT_ID=your_client_idNSHIFT_CLIENT_SECRET=your_client_secretNSHIFT_CONNECTION_ID=your_connection_idNSHIFT_LANGUAGE_CODE=enNSHIFT_LOCALE_ID=en-GBNSHIFT_DEFAULT_COUNTRY=FINSHIFT_DEFAULT_POSTAL_CODE=00100NSHIFT_SEND_TO_BOOK_AND_PRINT=false
If your client secret contains Copy to clipboard$, do not escape it as Copy to clipboard\$in Copy to clipboard.env— dotenv keeps the backslash and nShift answers Copy to clipboardinvalid_client. Wrap the value in quotes instead.
Provider options
Option Type Default Description Copy to clipboardclient_id Copy to clipboardstring — Required. nShift API Client ID. Copy to clipboardclient_secret Copy to clipboardstring — Required. nShift API Client Secret. Copy to clipboardconnection_id Copy to clipboardstring — Required. nShift Checkout Connection ID. Copy to clipboardlanguage_code Copy to clipboardstring Copy to clipboard"en" Language for option names and descriptions (ISO 639). Copy to clipboardlocale_id Copy to clipboardstring Copy to clipboard"en-GB" Locale for formatted price/date strings. Copy to clipboarddefault_country Copy to clipboardstring — Country used to list delivery options in the admin (ISO 3166-1 alpha-2). Required for the admin dropdown. Copy to clipboarddefault_postal_code Copy to clipboardstring — Postal code used to list delivery options in the admin. Required for the admin dropdown. Copy to clipboarddefault_state Copy to clipboardstring — State/province sent when the Medusa address has none. Some markets need it. Copy to clipboarddefault_currency Copy to clipboardstring Copy to clipboard"EUR" Currency used when the calculation context carries none. Copy to clipboardprices_tax_inclusive Copy to clipboardboolean Copy to clipboardtrue Whether nShift prices already include tax. Copy to clipboardweight_unit Copy to clipboard"g" | "kg" Copy to clipboard"kg" Unit of Copy to clipboardvariant.weight in your data. Set to Copy to clipboard"g" if you store grams. Copy to clipboarddimension_unit Copy to clipboard"mm" | "cm" | "m" Copy to clipboard"cm" Unit of Copy to clipboardvariant.length / Copy to clipboardwidth / Copy to clipboardheight. Copy to clipboarddefault_package_weight_kg Copy to clipboardnumber Copy to clipboard0.5 Weight sent when no item weights are known. Copy to clipboardsend_cart_total Copy to clipboardboolean Copy to clipboardtrue Send the basket value as Copy to clipboardtotalPrice so nShift price rules can evaluate. Copy to clipboardsend_to_book_and_print Copy to clipboardboolean Copy to clipboardfalse Push partial shipments to your Book & Print platform. Copy to clipboardinclude_in_conversion_rate Copy to clipboardboolean Copy to clipboardtrue Count sessions towards nShift's conversion-rate metric. Copy to clipboardrequest_timeout_ms Copy to clipboardnumber Copy to clipboard15000 Per-request timeout against the nShift API. Copy to clipboardoptions_cache_ttl_ms Copy to clipboardnumber Copy to clipboard30000 How long one session and its delivery options are reused for an unchanged cart. Copy to clipboardapi_base_url Copy to clipboardstring Copy to clipboardhttps://api.nshiftportal.com/checkout Override the nShift API host. Copy to clipboardauth_url Copy to clipboardstring Copy to clipboardhttps://account.nshiftportal.com/idp/connect/token Override the nShift token endpoint.
Check Copy to clipboardweight_unit. Medusa does not define a unit for Copy to clipboardvariant.weight. The default Copy to clipboard"kg" matches earlier versions of this plugin; if your catalogue stores grams, set Copy to clipboardweight_unit: "g" or nShift will price a 500 g shirt as 500 kg.
Check Copy to clipboardprices_tax_inclusive. nShift Checkout prices are normally the consumer-facing gross price, hence the Copy to clipboardtrue default. Set it to Copy to clipboardfalse if your configuration holds net prices, otherwise your cart's tax totals will be wrong.
Creating shipping options in the admin
- Settings → Locations & Shipping → your location → Shipping options → Create.
- Set Price type to Calculated so the price comes from nShift.
- Pick nShift as the provider and choose one of the delivery options it lists. Each Medusa shipping option maps to exactly one nShift delivery option.
The chosen delivery option is stored on Copy to clipboardshipping_option.data.option_id. If that dropdown is empty or errors, the message tells you why — most often a missing Copy to clipboarddefault_country / Copy to clipboarddefault_postal_code, or no delivery options configured for that country.
Storefront usage
Calculated options come back from Copy to clipboardGET /store/shipping-options without a price; fetch each price with Copy to clipboardPOST /store/shipping-options/:id/calculate, then add the method to the cart.
1234567891011121314151617181920import { sdk } from "../lib/config"// 1. List the options available for the cart.const { shipping_options } = await sdk.store.fulfillment.listCartOptions({cart_id: cart.id,})// 2. Price the calculated ones.const priced = await Promise.all(shipping_options.map(async (option) => {if (option.price_type !== "calculated") {return option}const { shipping_option } = await sdk.store.fulfillment.calculate(option.id, {cart_id: cart.id,data: {},})return shipping_option}))
Copy to clipboarddata accepted when adding a shipping method
Both snake_case and the nShift widget's camelCase spelling are accepted.
Key Alias Description Copy to clipboardpickup_point_id Copy to clipboardpickupPointId A Copy to clipboardpickupPointId from the option's Copy to clipboardpickupPoints. Copy to clipboardtime_slot_id Copy to clipboardtimeSlotId A Copy to clipboardtimeSlotId from the option's Copy to clipboardtimeSlots. Copy to clipboardaddons — Copy to clipboard[{ addon_id, fields?: [{ field_id, value }] }], or Copy to clipboard[{ addonId, ... }]. Copy to clipboardaddon_ids Copy to clipboardaddonIds Shorthand: Copy to clipboard["948058"]. Copy to clipboardfields — Copy to clipboard[{ field_id, value }] or a Copy to clipboard{ FIELDID: value } map.
Selections are validated against the delivery option before the method is stored: an unknown pickup point, time slot or addon is rejected with a Copy to clipboard400 naming it. Addon prices are added to the shipping price.
Anything else you put in Copy to clipboarddata is preserved verbatim, so you can carry your own state through checkout.
What ends up on the shipping method
12345678910111213141516{"session_id": "77933415-a756-4eab-821a-98fb6fc9aa75","option_id": "834827b2-abc2-4e05-8520-7707c1d2c4d8","carrier_id": "948","carrier_product_id": "10543","carrier_product_name": "Posti Home Parcel (2104)(10543) Finland only","price": 98,"currency_code": "EUR","addons": [{ "addonId": "948058" }],"delivery_time": {"earliest": "2026-08-29T00:00:00","latest": "2026-08-29T00:00:00","description": "Delivery on Saturday","timeZone": "Europe/Helsinki"}}
After fulfillment, Copy to clipboardfulfillment.data additionally carries Copy to clipboardnshift_shipment_id, Copy to clipboardnshift_order_id, Copy to clipboardnshift_carrier and Copy to clipboardnshift_carrier_product.
Behaviour and error handling
The provider never breaks a cart. Copy to clipboardcalculatePrice reports an option as unavailable — no Copy to clipboardcalculated_amount — instead of throwing, whenever:
- the cart has no country or postal code yet,
- nShift does not return the option for that address (wrong country route, weight over the limit, a price rule excluding it),
- nShift returns the option without a price,
- the nShift API is unreachable, times out or errors,
- the shipping option has no nShift delivery option attached.
Medusa treats a missing price as "not available in this context": Copy to clipboardrefreshCartShippingMethodsWorkflow removes the shipping method, and an explicit selection is rejected with Copy to clipboard400 … do not have a price. So changing the shipping country to one a carrier does not serve drops that method rather than failing the address update. Every case is logged with the nShift status and Copy to clipboardissues[] so you can see the reason in the server log.
Errors are raised where they are actionable instead:
Situation Result Missing Copy to clipboardclient_id / Copy to clipboardclient_secret / Copy to clipboardconnection_id throws at startup Admin option list without Copy to clipboarddefault_country / Copy to clipboarddefault_postal_code Copy to clipboard400 naming the missing option Admin option list, nShift request fails Copy to clipboard400 including the nShift status and issues Selecting an option nShift no longer offers Copy to clipboard400, pick another method Unknown pickup point, time slot or addon Copy to clipboard400 naming it Option requires a pickup point and none was sent Copy to clipboard400 Fulfilling without checkout session data Copy to clipboard400 Order address nShift cannot ship to Copy to clipboard400 nShift refuses to delete a cancelled shipment logged, cancellation still succeeds
Not covered yet
- Split shipments (nShift's Copy to clipboard
/split-shipments/*endpoints). - Return shipments — Copy to clipboard
createReturnFulfillmentis not implemented. - Labels and tracking numbers: booking and printing still happen in your Book & Print platform, so Copy to clipboard
createFulfillmentreturns no Copy to clipboardlabels. - Own Pickup Locations API management.
- Badges, certifications and Klarna delivery types are read from nShift but not surfaced on the shipping method.
License
MIT

