Skip to content

Fulfillment providers cannot read variant data through the admin fulfillment flow (create-fulfillment omits items.variant.metadata) #16621

Description

@FCraven

Summary

A fulfillment provider that needs per-line variant data — any print-on-demand or dropship integration, which is most non-manual providers — cannot get it through the admin fulfillment flow.

createOrderFulfillmentWorkflow hydrates the order from a fixed field list that omits items.variant.metadata, and the items argument handed to the provider is a list of persisted fulfillment_item rows — a model with no variant relation and no variant_id. The result: provider.createFulfillment receives no way to map an order line onto a vendor SKU.

Version: @medusajs/core-flows / @medusajs/fulfillment 2.19.0

What we hit

First real order through the admin flow, POST /admin/orders/:id/fulfillments, against a custom print-on-demand provider that reads item.variant?.metadata (the natural reading of the createFulfillment(data, items, order, fulfillment) signature):

400 invalid_data
Variant undefined is not mappable to a vendor SKU

item.variant_id was undefined — not merely item.variant.metadata. Every unit test passed, because fixtures construct hydrated items; the real caller never produces them.

Root cause, traced through 2.19.0

  1. create-fulfillment.ts — the order query omits variant metadata. useQueryGraphStep requests items.variant.manage_inventory, items.variant.sku, items.variant.weight, items.variant.hs_code, items.variant.origin_country … but never items.variant.metadata.
  2. prepareFulfillmentData flattens the lines. Each order item becomes { line_item_id, inventory_item_id, quantity, title, sku, barcode }. Anything else on the variant is dropped here regardless of what step 1 fetched.
  3. fulfillment_item cannot store it. The model (@medusajs/fulfillment/models/fulfillment-item) is { id, title, sku, barcode, quantity, line_item_id, inventory_item_id, fulfillment }. No variant, no variant_id.
  4. The provider receives those rows verbatim from FulfillmentModuleService.createFulfillment.

A provider cannot resolve the data for itself either: its container is the fulfillment module's, not the app's. Enumerated at runtime on 2.19.0 it holds the module's own repositories/services plus logger, manager, configModule, event_bus, caching, __pg_connection__ — no query, no remoteQuery, no product.

Proposed fix — one line, plus one docs paragraph

The order argument is passed to the provider untouched (prepareFulfillmentData returns order: order; the module service forwards it). So adding the field to the query list makes variant metadata reachable:

--- a/packages/core/core-flows/src/order/workflows/create-fulfillment.ts
+++ b/packages/core/core-flows/src/order/workflows/create-fulfillment.ts
@@ fields: [
       "items.variant.material",
+      "items.variant.metadata",
       "items.variant_title",

Verified against a live 2.19.0 instance — same order, same query engine:

field list items[0].variant keys variant.metadata
today manage_inventory, sku, weight, hs_code, id absent
with the patch manage_inventory, sku, weight, hs_code, metadata, id present

What the one-liner does not fix: it puts metadata on order.items[].variant.metadata, not on the items argument — fulfillment_item still cannot carry a variant. A provider must correlate items[].line_item_id → order.items[].id and read the variant from the order. That is not obvious from the signature, and the provider guide should say it outright. Suggested addition:

The items argument is a list of fulfillment_item records. It carries line_item_id, quantity, title, sku and barcode only — it has no variant relation. To read variant data (including metadata), match item.line_item_id against order.items[].id and read the variant from the order.

Happy to open the PR for the one-line change plus the docs paragraph — it's ready to go.

Two smaller observations from the same debugging session (unserialized entities handed to providers, and additional_data being the only documented-nowhere channel that survives the path) in a comment below, kept out of the main report so it stays single-purpose.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions