Shipping providers
Local and external shipping adapter dispatch for rates, labels, validation, tracking, and cancellation.
The ShippingProvider model and features/keystone/utils/shippingProviderAdapter.ts support:
- local modules
shippo,shipengineandmanualinfeatures/integrations/shipping; - separately deployed HTTP adapter apps, set per operation.
There are five operation fields: getRatesFunction, validateAddressFunction, createLabelFunction, trackShipmentFunction and cancelLabelFunction. When a field starts with http, Openfront sends that URL a JSON POST with the provider and the operation arguments. Otherwise it imports the matching local file and calls the export with the field's name.
Operations and who can call them
| Mutation | Adapter call | Permission |
|---|---|---|
getRatesForOrder(orderId, providerId, dimensions) | rates | canReadOrders or canManageOrders |
validateShippingAddress(providerId, address) | validate | canReadOrders or canManageOrders |
trackShipment(providerId, trackingNumber) | track | canReadOrders or canManageOrders |
createProviderShippingLabel(orderId, providerId, rateId, dimensions, lineItems, idempotencyKey) | label | canManageFulfillments |
cancelShippingLabel(providerId, labelId) | cancel | canManageFulfillments |
createProviderShippingLabel checks that each line has unfulfilled quantity, refuses inactive providers, and returns the existing label when the same idempotency key is retried. createOrderFulfillment and cancelOrderFulfillment record fulfillments without a carrier and also take an idempotency key and a reason respectively.
Data boundary
Shipping options define what customers can choose at checkout. Shipping providers, labels, fulfillments and fulfillment items record what was actually shipped. The callers read the provider's accessToken and send the provider record to the adapter. For an HTTP adapter, that app receives the access token and the order's address and line items.
The HTTP bridge sends a plain JSON POST. It has no timeout, request signature, destination allowlist or response schema. Restrict who can edit provider records, use HTTPS endpoints you control, and validate requests inside the adapter app.
Current gaps
- The order-level "new provider" form stores an API URL in
metadata.apiUrland does not set any operation field. - The provider drawer's "Custom Provider" option sets every operation to the local name
custom, but there is nocustom.tsmodule, so calls fail. - Set URL operation fields on the generic
/dashboard/shipping-providerseditor instead. - The
manualmodule is a placeholder. Its label function reads arateargument while the dispatcher passesrateId, and it returns made-up tracking numbers andexample.comlabel URLs.
A provider record existing does not mean its calls work. Run each operation you need.
Before enabling a provider
- Confirm each operation field points where you expect.
- Check that only trusted operators can read the access token or change the fields.
- Run rates, address validation, label purchase, tracking and cancellation in the carrier's sandbox.
- Retry a label purchase with the same idempotency key and confirm one label.
Manual fulfillment
Onboarding creates a manual fulfillment provider (fp_manual) for shipping options. Operators can record a fulfillment with an optional tracking number and carrier through createOrderFulfillment, without calling a carrier.
Start with Add a shipping adapter.