diff --git a/www/apps/api-reference/specs/admin/code_samples/Shell/admin_draft-orders_{id}/post.sh b/www/apps/api-reference/specs/admin/code_samples/Shell/admin_draft-orders_{id}/post.sh new file mode 100644 index 0000000000000..8a037122c901b --- /dev/null +++ b/www/apps/api-reference/specs/admin/code_samples/Shell/admin_draft-orders_{id}/post.sh @@ -0,0 +1,2 @@ +curl -X POST '{backend_url}/admin/draft-orders/{id}' \ +-H 'Authorization: Bearer {access_token}' \ No newline at end of file diff --git a/www/apps/api-reference/specs/admin/code_samples/Shell/admin_products_batch/post.sh b/www/apps/api-reference/specs/admin/code_samples/Shell/admin_products_batch/post.sh index 0475aa1407aef..5242e9ede81e8 100644 --- a/www/apps/api-reference/specs/admin/code_samples/Shell/admin_products_batch/post.sh +++ b/www/apps/api-reference/specs/admin/code_samples/Shell/admin_products_batch/post.sh @@ -1,2 +1,7 @@ curl -X POST '{backend_url}/admin/products/batch' \ --H 'Authorization: Bearer {access_token}' \ No newline at end of file +-H 'Authorization: Bearer {access_token}' \ +--data-raw '{ + "delete": [ + "prod_123" + ] +}' \ No newline at end of file diff --git a/www/apps/api-reference/specs/admin/components/schemas/AdminCreateProduct.yaml b/www/apps/api-reference/specs/admin/components/schemas/AdminCreateProduct.yaml index 0cf5d21e27c1b..0d83da3c0051a 100644 --- a/www/apps/api-reference/specs/admin/components/schemas/AdminCreateProduct.yaml +++ b/www/apps/api-reference/specs/admin/components/schemas/AdminCreateProduct.yaml @@ -3,7 +3,6 @@ description: The product's details. x-schemaName: AdminCreateProduct required: - title - - shipping_profile_id - options properties: title: diff --git a/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrder.yaml b/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrder.yaml new file mode 100644 index 0000000000000..0db0d9d34e77d --- /dev/null +++ b/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrder.yaml @@ -0,0 +1,249 @@ +type: object +description: The draft order's details. +x-schemaName: AdminDraftOrder +required: + - payment_collections + - items + - shipping_methods + - status + - currency_code + - id + - version + - region_id + - customer_id + - sales_channel_id + - email + - payment_status + - fulfillment_status + - summary + - created_at + - updated_at + - original_item_total + - original_item_subtotal + - original_item_tax_total + - item_total + - item_subtotal + - item_tax_total + - original_total + - original_subtotal + - original_tax_total + - total + - subtotal + - tax_total + - discount_total + - discount_tax_total + - gift_card_total + - gift_card_tax_total + - shipping_total + - shipping_subtotal + - shipping_tax_total + - original_shipping_total + - original_shipping_subtotal + - original_shipping_tax_total +properties: + payment_collections: + type: array + description: The draft order's payment collections. + items: + $ref: ./AdminPaymentCollection.yaml + fulfillments: + type: array + description: The draft order's fulfillments. + items: + $ref: ./AdminOrderFulfillment.yaml + sales_channel: + $ref: ./AdminSalesChannel.yaml + customer: + $ref: ./AdminCustomer.yaml + shipping_address: + $ref: ./AdminOrderAddress.yaml + billing_address: + $ref: ./AdminOrderAddress.yaml + items: + type: array + description: The draft order's items. + items: + $ref: ./AdminOrderLineItem.yaml + shipping_methods: + type: array + description: The draft order's shipping methods. + items: + $ref: ./AdminOrderShippingMethod.yaml + status: + type: string + title: status + description: The draft order's status. + currency_code: + type: string + title: currency_code + description: The draft order's currency code. + example: usd + id: + type: string + title: id + description: The draft order's ID. + version: + type: number + title: version + description: The draft order's version. + region_id: + type: string + title: region_id + description: The ID of the region associated with the draft order. + customer_id: + type: string + title: customer_id + description: The ID of the customer that the draft order belongs to. + sales_channel_id: + type: string + title: sales_channel_id + description: The ID of the sales channel that the draft order is placed in. + email: + type: string + title: email + description: The customer email associated with the draft order. + format: email + display_id: + type: number + title: display_id + description: The draft order's display ID. + payment_status: + type: string + description: The draft order's payment status. + enum: + - not_paid + - awaiting + - authorized + - partially_authorized + - canceled + - captured + - partially_captured + - partially_refunded + - refunded + - requires_action + fulfillment_status: + type: string + description: The draft order's fulfillment status. + enum: + - canceled + - not_fulfilled + - partially_fulfilled + - fulfilled + - partially_shipped + - shipped + - partially_delivered + - delivered + transactions: + type: array + description: The draft order's transactions. + items: + $ref: ./BaseOrderTransaction.yaml + summary: + $ref: ./BaseOrderSummary.yaml + metadata: + type: object + description: The draft order's metadata, can hold custom key-value pairs. + created_at: + type: string + format: date-time + title: created_at + description: The date the draft order was created. + updated_at: + type: string + format: date-time + title: updated_at + description: The date the draft order was updated. + original_item_total: + type: number + title: original_item_total + description: >- + The total of the draft order's items including taxes, excluding + promotions. + original_item_subtotal: + type: number + title: original_item_subtotal + description: >- + The total of the draft order's items excluding taxes, including + promotions. + original_item_tax_total: + type: number + title: original_item_tax_total + description: The tax total of the draft order's items excluding promotions. + item_total: + type: number + title: item_total + description: The total of the draft order's items including taxes and promotions. + item_subtotal: + type: number + title: item_subtotal + description: >- + The total of the draft order's items excluding taxes, including + promotions. + item_tax_total: + type: number + title: item_tax_total + description: The tax total of the draft order's items including promotions. + original_total: + type: number + title: original_total + description: The draft order's total excluding promotions, including taxes. + original_subtotal: + type: number + title: original_subtotal + description: The draft order's total excluding taxes, including promotions. + original_tax_total: + type: number + title: original_tax_total + description: The draft order's tax total, excluding promotions. + total: + type: number + title: total + description: The draft order's total including taxes and promotions. + subtotal: + type: number + title: subtotal + description: The draft order's total excluding taxes, including promotions. + tax_total: + type: number + title: tax_total + description: The draft order's tax total including promotions. + discount_total: + type: number + title: discount_total + description: The draft order's discount or promotions total. + discount_tax_total: + type: number + title: discount_tax_total + description: The tax total of draft order's discount or promotion. + gift_card_total: + type: number + title: gift_card_total + description: The draft order's gift card total. + gift_card_tax_total: + type: number + title: gift_card_tax_total + description: The tax total of the draft order's gift card. + shipping_total: + type: number + title: shipping_total + description: The draft order's shipping total including taxes and promotions. + shipping_subtotal: + type: number + title: shipping_subtotal + description: The draft order's shipping total excluding taxes, including promotions. + shipping_tax_total: + type: number + title: shipping_tax_total + description: The tax total of the draft order's shipping. + original_shipping_total: + type: number + title: original_shipping_total + description: The draft order's shipping total including taxes, excluding promotions. + original_shipping_subtotal: + type: number + title: original_shipping_subtotal + description: The draft order's shipping total excluding taxes, including promotions. + original_shipping_tax_total: + type: number + title: original_shipping_tax_total + description: The tax total of the draft order's shipping excluding promotions. diff --git a/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrderListResponse.yaml b/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrderListResponse.yaml new file mode 100644 index 0000000000000..1973bfc0ee3bd --- /dev/null +++ b/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrderListResponse.yaml @@ -0,0 +1,26 @@ +type: object +description: The list of draft orders with pagination fields. +x-schemaName: AdminDraftOrderListResponse +required: + - limit + - offset + - count + - draft_orders +properties: + limit: + type: number + title: limit + description: The maximum number of items retrieved. + offset: + type: number + title: offset + description: The number of items skipped before retrieving the returned items. + count: + type: number + title: count + description: The total count of items available. + draft_orders: + type: array + description: The list of draft orders. + items: + $ref: ./AdminDraftOrder.yaml diff --git a/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrderResponse.yaml b/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrderResponse.yaml index e10135e61d015..fa2dd4e372826 100644 --- a/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrderResponse.yaml +++ b/www/apps/api-reference/specs/admin/components/schemas/AdminDraftOrderResponse.yaml @@ -5,4 +5,4 @@ required: - draft_order properties: draft_order: - $ref: ./AdminOrder.yaml + $ref: ./AdminDraftOrder.yaml diff --git a/www/apps/api-reference/specs/admin/components/schemas/AdminUpdateDraftOrder.yaml b/www/apps/api-reference/specs/admin/components/schemas/AdminUpdateDraftOrder.yaml new file mode 100644 index 0000000000000..e324201489280 --- /dev/null +++ b/www/apps/api-reference/specs/admin/components/schemas/AdminUpdateDraftOrder.yaml @@ -0,0 +1,108 @@ +type: object +description: The data to update in the draft order. +x-schemaName: AdminUpdateDraftOrder +properties: + email: + type: string + title: email + description: The customer email associated with the draft order. + format: email + shipping_address: + type: object + description: The draft order's shipping address. + properties: + first_name: + type: string + title: first_name + description: The shipping address's first name. + last_name: + type: string + title: last_name + description: The shipping address's last name. + phone: + type: string + title: phone + description: The shipping address's phone. + company: + type: string + title: company + description: The shipping address's company. + address_1: + type: string + title: address_1 + description: The first address line. + address_2: + type: string + title: address_2 + description: The second address line. + city: + type: string + title: city + description: The shipping address's city. + country_code: + type: string + title: country_code + description: The shipping address's country code. + example: us + province: + type: string + title: province + description: The shipping address's province. + postal_code: + type: string + title: postal_code + description: The shipping address's postal code. + metadata: + type: object + description: The shipping address's metadata, can hold custom key-value pairs. + billing_address: + type: object + description: The draft order's billing address. + properties: + first_name: + type: string + title: first_name + description: The billing address's first name. + last_name: + type: string + title: last_name + description: The billing address's last name. + phone: + type: string + title: phone + description: The billing address's phone. + company: + type: string + title: company + description: The billing address's company. + address_1: + type: string + title: address_1 + description: The first address line. + address_2: + type: string + title: address_2 + description: The second address line. + city: + type: string + title: city + description: The billing address's city. + country_code: + type: string + title: country_code + description: The billing address's country code. + example: us + province: + type: string + title: province + description: The billing address's province. + postal_code: + type: string + title: postal_code + description: The billing address's postal code. + metadata: + type: object + description: The billing address's metadata, can hold custom key-value pairs. + metadata: + type: object + description: The draft order's metadata, can hold custom key-value pairs. diff --git a/www/apps/api-reference/specs/admin/components/schemas/BaseOrderSummary.yaml b/www/apps/api-reference/specs/admin/components/schemas/BaseOrderSummary.yaml index 26db0db078bdd..acc9cf510f48e 100644 --- a/www/apps/api-reference/specs/admin/components/schemas/BaseOrderSummary.yaml +++ b/www/apps/api-reference/specs/admin/components/schemas/BaseOrderSummary.yaml @@ -2,49 +2,14 @@ type: object description: The order's summary details. x-schemaName: BaseOrderSummary required: - - total - - subtotal - - total_tax - - ordered_total - - fulfilled_total - - returned_total - - return_request_total - - write_off_total + - pending_difference + - current_order_total + - original_order_total + - transaction_total - paid_total - refunded_total + - accounting_total properties: - total: - type: number - title: total - description: The order's total including taxes and promotions. - subtotal: - type: number - title: subtotal - description: The order's total excluding taxes, including promotions. - total_tax: - type: number - title: total_tax - description: The order's total taxes. - ordered_total: - type: number - title: ordered_total - description: The order's total when it was placed. - fulfilled_total: - type: number - title: fulfilled_total - description: The total of the fulfilled items of the order. - returned_total: - type: number - title: returned_total - description: The total of the order's returned items. - return_request_total: - type: number - title: return_request_total - description: The total of the items requested to be returned. - write_off_total: - type: number - title: write_off_total - description: The total of the items removed from the order. paid_total: type: number title: paid_total @@ -53,3 +18,25 @@ properties: type: number title: refunded_total description: The total amount refunded. + pending_difference: + type: number + title: pending_difference + description: >- + The difference pending to be processed. If negative, the customer needs a + refund. Otherwise, additional payment is required from the customer. + current_order_total: + type: number + title: current_order_total + description: The order's current total, could be the total after a change in the order. + original_order_total: + type: number + title: original_order_total + description: The order's original total. + transaction_total: + type: number + title: transaction_total + description: The total of the transactions made on the order. + accounting_total: + type: number + title: accounting_total + description: The order's total without the credit-line total. diff --git a/www/apps/api-reference/specs/admin/components/schemas/BasePaymentCollection.yaml b/www/apps/api-reference/specs/admin/components/schemas/BasePaymentCollection.yaml index 1dbd0354387dd..e5c542339e375 100644 --- a/www/apps/api-reference/specs/admin/components/schemas/BasePaymentCollection.yaml +++ b/www/apps/api-reference/specs/admin/components/schemas/BasePaymentCollection.yaml @@ -59,6 +59,8 @@ properties: - awaiting - authorized - partially_authorized + - completed + - failed payment_providers: type: array description: >- diff --git a/www/apps/api-reference/specs/admin/components/schemas/BasePaymentSession.yaml b/www/apps/api-reference/specs/admin/components/schemas/BasePaymentSession.yaml index 33533c274e3c9..b03530fb1b30a 100644 --- a/www/apps/api-reference/specs/admin/components/schemas/BasePaymentSession.yaml +++ b/www/apps/api-reference/specs/admin/components/schemas/BasePaymentSession.yaml @@ -1,6 +1,13 @@ type: object description: The payment session's details. x-schemaName: BasePaymentSession +required: + - id + - amount + - currency_code + - provider_id + - data + - status properties: id: type: string @@ -26,7 +33,7 @@ properties: payment. externalDocs: url: >- - https://docs.medusajs.com/v2/resources/commerce-modules/payment/payment-session#data-property + https://docs.medusajs.com/resources/commerce-modules/payment/payment-session#data-property context: type: object description: The context around the payment, such as the customer's details. @@ -37,12 +44,12 @@ properties: type: string description: The payment session's status. enum: + - error - authorized - - captured - canceled + - captured - pending - requires_more - - error authorized_at: type: string title: authorized_at @@ -52,10 +59,3 @@ properties: type: object payment: $ref: ./BasePayment.yaml -required: - - id - - amount - - currency_code - - provider_id - - data - - status diff --git a/www/apps/api-reference/specs/admin/components/schemas/OrderTransaction.yaml b/www/apps/api-reference/specs/admin/components/schemas/OrderTransaction.yaml index 39323d61139f4..d497d2305cd4f 100644 --- a/www/apps/api-reference/specs/admin/components/schemas/OrderTransaction.yaml +++ b/www/apps/api-reference/specs/admin/components/schemas/OrderTransaction.yaml @@ -4,6 +4,7 @@ x-schemaName: OrderTransaction required: - id - order_id + - version - order - amount - currency_code @@ -59,3 +60,7 @@ properties: description: The date that the transaction was updated. order: type: object + version: + type: number + title: version + description: The order version that the transaction belongs to. diff --git a/www/apps/api-reference/specs/admin/components/schemas/StoreCartShippingOption.yaml b/www/apps/api-reference/specs/admin/components/schemas/StoreCartShippingOption.yaml index 77bd8ba167edc..fc89157eaa39a 100644 --- a/www/apps/api-reference/specs/admin/components/schemas/StoreCartShippingOption.yaml +++ b/www/apps/api-reference/specs/admin/components/schemas/StoreCartShippingOption.yaml @@ -14,6 +14,7 @@ required: - amount - prices - calculated_price + - insufficient_inventory properties: id: type: string @@ -101,3 +102,9 @@ properties: $ref: ./StorePrice.yaml calculated_price: $ref: ./StoreCalculatedPrice.yaml + insufficient_inventory: + type: boolean + title: insufficient_inventory + description: >- + Whether the shipping option's location doesn't have sufficient quantity + for any of the cart's items. diff --git a/www/apps/api-reference/specs/admin/openapi.full.yaml b/www/apps/api-reference/specs/admin/openapi.full.yaml index f6f2f0b7b2787..7bc10669ba599 100644 --- a/www/apps/api-reference/specs/admin/openapi.full.yaml +++ b/www/apps/api-reference/specs/admin/openapi.full.yaml @@ -91,6 +91,8 @@ tags: externalDocs: description: Learn more about the Order Module url: https://docs.medusajs.com/v2/resources/commerce-modules/order + x-associatedSchema: + $ref: '#/components/schemas/AdminDraftOrder' - name: Exchanges description: | An exchange is the replacement of an item that the customer ordered with another. @@ -12985,33 +12987,7 @@ paths: content: application/json: schema: - allOf: - - type: object - description: The paginated list of draft orders. - required: - - limit - - offset - - count - properties: - limit: - type: number - title: limit - description: The maximum number of items returned. - offset: - type: number - title: offset - description: The number of items skipped before retrieving the returned items. - count: - type: number - title: count - description: The total number of items. - - type: object - description: The paginated list of draft orders. - required: - - draft_orders - properties: - draft_orders: - $ref: '#/components/schemas/AdminOrder' + $ref: '#/components/schemas/AdminDraftOrderListResponse' '400': $ref: '#/components/responses/400_error' '401': @@ -13024,6 +13000,7 @@ paths: $ref: '#/components/responses/invalid_request_error' '500': $ref: '#/components/responses/500_error' + x-workflow: getOrdersListWorkflow post: operationId: PostDraftOrders summary: Create Draft Order @@ -13394,6 +13371,19 @@ paths: required: true schema: type: string + - name: fields + in: query + description: |- + Comma-separated fields that should be included in the returned data. + if a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default fields. + without prefix it will replace the entire default fields. + required: false + schema: + type: string + title: fields + description: Comma-separated fields that should be included in the returned data. If a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default fields. Without prefix it will replace the entire default fields. + externalDocs: + url: '#select-fields-and-relations' security: - api_token: [] - cookie_auth: [] @@ -13425,6 +13415,69 @@ paths: $ref: '#/components/responses/invalid_request_error' '500': $ref: '#/components/responses/500_error' + x-workflow: getOrderDetailWorkflow + post: + operationId: PostDraftOrdersId + summary: Update a Draft Order + description: Update a draft order's details. + x-authenticated: true + parameters: + - name: id + in: path + description: The draft order's ID. + required: true + schema: + type: string + - name: fields + in: query + description: |- + Comma-separated fields that should be included in the returned data. + if a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default fields. + without prefix it will replace the entire default fields. + required: false + schema: + type: string + title: fields + description: Comma-separated fields that should be included in the returned data. If a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default fields. Without prefix it will replace the entire default fields. + externalDocs: + url: '#select-fields-and-relations' + security: + - api_token: [] + - cookie_auth: [] + - jwt_token: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AdminUpdateDraftOrder' + x-codeSamples: + - lang: Shell + label: cURL + source: |- + curl -X POST '{backend_url}/admin/draft-orders/{id}' \ + -H 'Authorization: Bearer {access_token}' + tags: + - Draft Orders + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AdminDraftOrderResponse' + '400': + $ref: '#/components/responses/400_error' + '401': + $ref: '#/components/responses/unauthorized' + '404': + $ref: '#/components/responses/not_found_error' + '409': + $ref: '#/components/responses/invalid_state_error' + '422': + $ref: '#/components/responses/invalid_request_error' + '500': + $ref: '#/components/responses/500_error' + x-workflow: updateOrderWorkflow /admin/exchanges: get: operationId: GetExchanges @@ -27060,7 +27113,12 @@ paths: label: cURL source: |- curl -X POST '{backend_url}/admin/products/batch' \ - -H 'Authorization: Bearer {access_token}' + -H 'Authorization: Bearer {access_token}' \ + --data-raw '{ + "delete": [ + "prod_123" + ] + }' tags: - Products responses: @@ -45107,7 +45165,6 @@ components: x-schemaName: AdminCreateProduct required: - title - - shipping_profile_id - options properties: title: @@ -46963,6 +47020,277 @@ components: type: boolean title: deleted description: Whether the object was deleted. + AdminDraftOrder: + type: object + description: The draft order's details. + x-schemaName: AdminDraftOrder + required: + - payment_collections + - items + - shipping_methods + - status + - currency_code + - id + - version + - region_id + - customer_id + - sales_channel_id + - email + - payment_status + - fulfillment_status + - summary + - created_at + - updated_at + - original_item_total + - original_item_subtotal + - original_item_tax_total + - item_total + - item_subtotal + - item_tax_total + - original_total + - original_subtotal + - original_tax_total + - total + - subtotal + - tax_total + - discount_total + - discount_tax_total + - gift_card_total + - gift_card_tax_total + - shipping_total + - shipping_subtotal + - shipping_tax_total + - original_shipping_total + - original_shipping_subtotal + - original_shipping_tax_total + properties: + payment_collections: + type: array + description: The draft order's payment collections. + items: + $ref: '#/components/schemas/AdminPaymentCollection' + fulfillments: + type: array + description: The draft order's fulfillments. + items: + $ref: '#/components/schemas/AdminOrderFulfillment' + sales_channel: + $ref: '#/components/schemas/AdminSalesChannel' + customer: + $ref: '#/components/schemas/AdminCustomer' + shipping_address: + $ref: '#/components/schemas/AdminOrderAddress' + billing_address: + $ref: '#/components/schemas/AdminOrderAddress' + items: + type: array + description: The draft order's items. + items: + $ref: '#/components/schemas/AdminOrderLineItem' + shipping_methods: + type: array + description: The draft order's shipping methods. + items: + $ref: '#/components/schemas/AdminOrderShippingMethod' + status: + type: string + title: status + description: The draft order's status. + currency_code: + type: string + title: currency_code + description: The draft order's currency code. + example: usd + id: + type: string + title: id + description: The draft order's ID. + version: + type: number + title: version + description: The draft order's version. + region_id: + type: string + title: region_id + description: The ID of the region associated with the draft order. + customer_id: + type: string + title: customer_id + description: The ID of the customer that the draft order belongs to. + sales_channel_id: + type: string + title: sales_channel_id + description: The ID of the sales channel that the draft order is placed in. + email: + type: string + title: email + description: The customer email associated with the draft order. + format: email + display_id: + type: number + title: display_id + description: The draft order's display ID. + payment_status: + type: string + description: The draft order's payment status. + enum: + - not_paid + - awaiting + - authorized + - partially_authorized + - canceled + - captured + - partially_captured + - partially_refunded + - refunded + - requires_action + fulfillment_status: + type: string + description: The draft order's fulfillment status. + enum: + - canceled + - not_fulfilled + - partially_fulfilled + - fulfilled + - partially_shipped + - shipped + - partially_delivered + - delivered + transactions: + type: array + description: The draft order's transactions. + items: + $ref: '#/components/schemas/BaseOrderTransaction' + summary: + $ref: '#/components/schemas/BaseOrderSummary' + metadata: + type: object + description: The draft order's metadata, can hold custom key-value pairs. + created_at: + type: string + format: date-time + title: created_at + description: The date the draft order was created. + updated_at: + type: string + format: date-time + title: updated_at + description: The date the draft order was updated. + original_item_total: + type: number + title: original_item_total + description: The total of the draft order's items including taxes, excluding promotions. + original_item_subtotal: + type: number + title: original_item_subtotal + description: The total of the draft order's items excluding taxes, including promotions. + original_item_tax_total: + type: number + title: original_item_tax_total + description: The tax total of the draft order's items excluding promotions. + item_total: + type: number + title: item_total + description: The total of the draft order's items including taxes and promotions. + item_subtotal: + type: number + title: item_subtotal + description: The total of the draft order's items excluding taxes, including promotions. + item_tax_total: + type: number + title: item_tax_total + description: The tax total of the draft order's items including promotions. + original_total: + type: number + title: original_total + description: The draft order's total excluding promotions, including taxes. + original_subtotal: + type: number + title: original_subtotal + description: The draft order's total excluding taxes, including promotions. + original_tax_total: + type: number + title: original_tax_total + description: The draft order's tax total, excluding promotions. + total: + type: number + title: total + description: The draft order's total including taxes and promotions. + subtotal: + type: number + title: subtotal + description: The draft order's total excluding taxes, including promotions. + tax_total: + type: number + title: tax_total + description: The draft order's tax total including promotions. + discount_total: + type: number + title: discount_total + description: The draft order's discount or promotions total. + discount_tax_total: + type: number + title: discount_tax_total + description: The tax total of draft order's discount or promotion. + gift_card_total: + type: number + title: gift_card_total + description: The draft order's gift card total. + gift_card_tax_total: + type: number + title: gift_card_tax_total + description: The tax total of the draft order's gift card. + shipping_total: + type: number + title: shipping_total + description: The draft order's shipping total including taxes and promotions. + shipping_subtotal: + type: number + title: shipping_subtotal + description: The draft order's shipping total excluding taxes, including promotions. + shipping_tax_total: + type: number + title: shipping_tax_total + description: The tax total of the draft order's shipping. + original_shipping_total: + type: number + title: original_shipping_total + description: The draft order's shipping total including taxes, excluding promotions. + original_shipping_subtotal: + type: number + title: original_shipping_subtotal + description: The draft order's shipping total excluding taxes, including promotions. + original_shipping_tax_total: + type: number + title: original_shipping_tax_total + description: The tax total of the draft order's shipping excluding promotions. + AdminDraftOrderListResponse: + type: object + description: The list of draft orders with pagination fields. + x-schemaName: AdminDraftOrderListResponse + required: + - limit + - offset + - count + - draft_orders + properties: + limit: + type: number + title: limit + description: The maximum number of items retrieved. + offset: + type: number + title: offset + description: The number of items skipped before retrieving the returned items. + count: + type: number + title: count + description: The total count of items available. + draft_orders: + type: array + description: The list of draft orders. + items: + $ref: '#/components/schemas/AdminDraftOrder' AdminDraftOrderResponse: type: object description: The draft order's details. @@ -46971,7 +47299,7 @@ components: - draft_order properties: draft_order: - $ref: '#/components/schemas/AdminOrder' + $ref: '#/components/schemas/AdminDraftOrder' AdminExchange: type: object description: The exchange's details. @@ -54065,6 +54393,115 @@ components: metadata: type: object description: The customer group's metadata, can hold custom key-value pairs. + AdminUpdateDraftOrder: + type: object + description: The data to update in the draft order. + x-schemaName: AdminUpdateDraftOrder + properties: + email: + type: string + title: email + description: The customer email associated with the draft order. + format: email + shipping_address: + type: object + description: The draft order's shipping address. + properties: + first_name: + type: string + title: first_name + description: The shipping address's first name. + last_name: + type: string + title: last_name + description: The shipping address's last name. + phone: + type: string + title: phone + description: The shipping address's phone. + company: + type: string + title: company + description: The shipping address's company. + address_1: + type: string + title: address_1 + description: The first address line. + address_2: + type: string + title: address_2 + description: The second address line. + city: + type: string + title: city + description: The shipping address's city. + country_code: + type: string + title: country_code + description: The shipping address's country code. + example: us + province: + type: string + title: province + description: The shipping address's province. + postal_code: + type: string + title: postal_code + description: The shipping address's postal code. + metadata: + type: object + description: The shipping address's metadata, can hold custom key-value pairs. + billing_address: + type: object + description: The draft order's billing address. + properties: + first_name: + type: string + title: first_name + description: The billing address's first name. + last_name: + type: string + title: last_name + description: The billing address's last name. + phone: + type: string + title: phone + description: The billing address's phone. + company: + type: string + title: company + description: The billing address's company. + address_1: + type: string + title: address_1 + description: The first address line. + address_2: + type: string + title: address_2 + description: The second address line. + city: + type: string + title: city + description: The billing address's city. + country_code: + type: string + title: country_code + description: The billing address's country code. + example: us + province: + type: string + title: province + description: The billing address's province. + postal_code: + type: string + title: postal_code + description: The billing address's postal code. + metadata: + type: object + description: The billing address's metadata, can hold custom key-value pairs. + metadata: + type: object + description: The draft order's metadata, can hold custom key-value pairs. AdminUpdateOrder: type: object description: The details to update in the order. @@ -57220,49 +57657,14 @@ components: description: The order's summary details. x-schemaName: BaseOrderSummary required: - - total - - subtotal - - total_tax - - ordered_total - - fulfilled_total - - returned_total - - return_request_total - - write_off_total + - pending_difference + - current_order_total + - original_order_total + - transaction_total - paid_total - refunded_total + - accounting_total properties: - total: - type: number - title: total - description: The order's total including taxes and promotions. - subtotal: - type: number - title: subtotal - description: The order's total excluding taxes, including promotions. - total_tax: - type: number - title: total_tax - description: The order's total taxes. - ordered_total: - type: number - title: ordered_total - description: The order's total when it was placed. - fulfilled_total: - type: number - title: fulfilled_total - description: The total of the fulfilled items of the order. - returned_total: - type: number - title: returned_total - description: The total of the order's returned items. - return_request_total: - type: number - title: return_request_total - description: The total of the items requested to be returned. - write_off_total: - type: number - title: write_off_total - description: The total of the items removed from the order. paid_total: type: number title: paid_total @@ -57271,6 +57673,26 @@ components: type: number title: refunded_total description: The total amount refunded. + pending_difference: + type: number + title: pending_difference + description: The difference pending to be processed. If negative, the customer needs a refund. Otherwise, additional payment is required from the customer. + current_order_total: + type: number + title: current_order_total + description: The order's current total, could be the total after a change in the order. + original_order_total: + type: number + title: original_order_total + description: The order's original total. + transaction_total: + type: number + title: transaction_total + description: The total of the transactions made on the order. + accounting_total: + type: number + title: accounting_total + description: The order's total without the credit-line total. BaseOrderTransaction: type: object description: An order transaction's details. @@ -57466,6 +57888,8 @@ components: - awaiting - authorized - partially_authorized + - completed + - failed payment_providers: type: array description: The payment provider used to process the collection's payments and sessions. @@ -57496,6 +57920,13 @@ components: type: object description: The payment session's details. x-schemaName: BasePaymentSession + required: + - id + - amount + - currency_code + - provider_id + - data + - status properties: id: type: string @@ -57518,7 +57949,7 @@ components: type: object description: The payment session's data, useful for the payment provider processing the payment. externalDocs: - url: https://docs.medusajs.com/v2/resources/commerce-modules/payment/payment-session#data-property + url: https://docs.medusajs.com/resources/commerce-modules/payment/payment-session#data-property context: type: object description: The context around the payment, such as the customer's details. @@ -57529,12 +57960,12 @@ components: type: string description: The payment session's status. enum: + - error - authorized - - captured - canceled + - captured - pending - requires_more - - error authorized_at: type: string title: authorized_at @@ -57544,13 +57975,6 @@ components: type: object payment: $ref: '#/components/schemas/BasePayment' - required: - - id - - amount - - currency_code - - provider_id - - data - - status BaseProduct: type: object description: The product's details. @@ -60459,6 +60883,7 @@ components: required: - id - order_id + - version - order - amount - currency_code @@ -60511,6 +60936,10 @@ components: description: The date that the transaction was updated. order: type: object + version: + type: number + title: version + description: The order version that the transaction belongs to. RefundReason: type: object description: The refund reason's details. @@ -61743,6 +62172,7 @@ components: - amount - prices - calculated_price + - insufficient_inventory properties: id: type: string @@ -61826,6 +62256,10 @@ components: $ref: '#/components/schemas/StorePrice' calculated_price: $ref: '#/components/schemas/StoreCalculatedPrice' + insufficient_inventory: + type: boolean + title: insufficient_inventory + description: Whether the shipping option's location doesn't have sufficient quantity for any of the cart's items. StoreCollection: type: object description: The collection's details. diff --git a/www/apps/api-reference/specs/admin/openapi.yaml b/www/apps/api-reference/specs/admin/openapi.yaml index dec077e7813ad..23edee44a2c46 100644 --- a/www/apps/api-reference/specs/admin/openapi.yaml +++ b/www/apps/api-reference/specs/admin/openapi.yaml @@ -119,6 +119,8 @@ tags: externalDocs: description: Learn more about the Order Module url: https://docs.medusajs.com/v2/resources/commerce-modules/order + x-associatedSchema: + $ref: ./components/schemas/AdminDraftOrder.yaml - name: Exchanges description: > An exchange is the replacement of an item that the customer ordered with diff --git a/www/apps/api-reference/specs/admin/paths/admin_draft-orders.yaml b/www/apps/api-reference/specs/admin/paths/admin_draft-orders.yaml index 3d78e7cfa5b83..53de5eb579a57 100644 --- a/www/apps/api-reference/specs/admin/paths/admin_draft-orders.yaml +++ b/www/apps/api-reference/specs/admin/paths/admin_draft-orders.yaml @@ -1804,35 +1804,7 @@ get: content: application/json: schema: - allOf: - - type: object - description: The paginated list of draft orders. - required: - - limit - - offset - - count - properties: - limit: - type: number - title: limit - description: The maximum number of items returned. - offset: - type: number - title: offset - description: >- - The number of items skipped before retrieving the returned - items. - count: - type: number - title: count - description: The total number of items. - - type: object - description: The paginated list of draft orders. - required: - - draft_orders - properties: - draft_orders: - $ref: ../components/schemas/AdminOrder.yaml + $ref: ../components/schemas/AdminDraftOrderListResponse.yaml '400': $ref: ../components/responses/400_error.yaml '401': @@ -1845,6 +1817,7 @@ get: $ref: ../components/responses/invalid_request_error.yaml '500': $ref: ../components/responses/500_error.yaml + x-workflow: getOrdersListWorkflow post: operationId: PostDraftOrders summary: Create Draft Order diff --git a/www/apps/api-reference/specs/admin/paths/admin_draft-orders_{id}.yaml b/www/apps/api-reference/specs/admin/paths/admin_draft-orders_{id}.yaml index 1e96c09036015..9db46ee8013c0 100644 --- a/www/apps/api-reference/specs/admin/paths/admin_draft-orders_{id}.yaml +++ b/www/apps/api-reference/specs/admin/paths/admin_draft-orders_{id}.yaml @@ -12,6 +12,26 @@ get: required: true schema: type: string + - name: fields + in: query + description: >- + Comma-separated fields that should be included in the returned data. + + if a field is prefixed with `+` it will be added to the default fields, + using `-` will remove it from the default fields. + + without prefix it will replace the entire default fields. + required: false + schema: + type: string + title: fields + description: >- + Comma-separated fields that should be included in the returned data. + If a field is prefixed with `+` it will be added to the default + fields, using `-` will remove it from the default fields. Without + prefix it will replace the entire default fields. + externalDocs: + url: '#select-fields-and-relations' security: - api_token: [] - cookie_auth: [] @@ -42,3 +62,72 @@ get: $ref: ../components/responses/invalid_request_error.yaml '500': $ref: ../components/responses/500_error.yaml + x-workflow: getOrderDetailWorkflow +post: + operationId: PostDraftOrdersId + summary: Update a Draft Order + description: Update a draft order's details. + x-authenticated: true + parameters: + - name: id + in: path + description: The draft order's ID. + required: true + schema: + type: string + - name: fields + in: query + description: >- + Comma-separated fields that should be included in the returned data. + + if a field is prefixed with `+` it will be added to the default fields, + using `-` will remove it from the default fields. + + without prefix it will replace the entire default fields. + required: false + schema: + type: string + title: fields + description: >- + Comma-separated fields that should be included in the returned data. + If a field is prefixed with `+` it will be added to the default + fields, using `-` will remove it from the default fields. Without + prefix it will replace the entire default fields. + externalDocs: + url: '#select-fields-and-relations' + security: + - api_token: [] + - cookie_auth: [] + - jwt_token: [] + requestBody: + content: + application/json: + schema: + $ref: ../components/schemas/AdminUpdateDraftOrder.yaml + x-codeSamples: + - lang: Shell + label: cURL + source: + $ref: ../code_samples/Shell/admin_draft-orders_{id}/post.sh + tags: + - Draft Orders + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: ../components/schemas/AdminDraftOrderResponse.yaml + '400': + $ref: ../components/responses/400_error.yaml + '401': + $ref: ../components/responses/unauthorized.yaml + '404': + $ref: ../components/responses/not_found_error.yaml + '409': + $ref: ../components/responses/invalid_state_error.yaml + '422': + $ref: ../components/responses/invalid_request_error.yaml + '500': + $ref: ../components/responses/500_error.yaml + x-workflow: updateOrderWorkflow diff --git a/www/apps/api-reference/specs/store/components/schemas/AdminCreateProduct.yaml b/www/apps/api-reference/specs/store/components/schemas/AdminCreateProduct.yaml index 0cf5d21e27c1b..0d83da3c0051a 100644 --- a/www/apps/api-reference/specs/store/components/schemas/AdminCreateProduct.yaml +++ b/www/apps/api-reference/specs/store/components/schemas/AdminCreateProduct.yaml @@ -3,7 +3,6 @@ description: The product's details. x-schemaName: AdminCreateProduct required: - title - - shipping_profile_id - options properties: title: diff --git a/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrder.yaml b/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrder.yaml new file mode 100644 index 0000000000000..0db0d9d34e77d --- /dev/null +++ b/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrder.yaml @@ -0,0 +1,249 @@ +type: object +description: The draft order's details. +x-schemaName: AdminDraftOrder +required: + - payment_collections + - items + - shipping_methods + - status + - currency_code + - id + - version + - region_id + - customer_id + - sales_channel_id + - email + - payment_status + - fulfillment_status + - summary + - created_at + - updated_at + - original_item_total + - original_item_subtotal + - original_item_tax_total + - item_total + - item_subtotal + - item_tax_total + - original_total + - original_subtotal + - original_tax_total + - total + - subtotal + - tax_total + - discount_total + - discount_tax_total + - gift_card_total + - gift_card_tax_total + - shipping_total + - shipping_subtotal + - shipping_tax_total + - original_shipping_total + - original_shipping_subtotal + - original_shipping_tax_total +properties: + payment_collections: + type: array + description: The draft order's payment collections. + items: + $ref: ./AdminPaymentCollection.yaml + fulfillments: + type: array + description: The draft order's fulfillments. + items: + $ref: ./AdminOrderFulfillment.yaml + sales_channel: + $ref: ./AdminSalesChannel.yaml + customer: + $ref: ./AdminCustomer.yaml + shipping_address: + $ref: ./AdminOrderAddress.yaml + billing_address: + $ref: ./AdminOrderAddress.yaml + items: + type: array + description: The draft order's items. + items: + $ref: ./AdminOrderLineItem.yaml + shipping_methods: + type: array + description: The draft order's shipping methods. + items: + $ref: ./AdminOrderShippingMethod.yaml + status: + type: string + title: status + description: The draft order's status. + currency_code: + type: string + title: currency_code + description: The draft order's currency code. + example: usd + id: + type: string + title: id + description: The draft order's ID. + version: + type: number + title: version + description: The draft order's version. + region_id: + type: string + title: region_id + description: The ID of the region associated with the draft order. + customer_id: + type: string + title: customer_id + description: The ID of the customer that the draft order belongs to. + sales_channel_id: + type: string + title: sales_channel_id + description: The ID of the sales channel that the draft order is placed in. + email: + type: string + title: email + description: The customer email associated with the draft order. + format: email + display_id: + type: number + title: display_id + description: The draft order's display ID. + payment_status: + type: string + description: The draft order's payment status. + enum: + - not_paid + - awaiting + - authorized + - partially_authorized + - canceled + - captured + - partially_captured + - partially_refunded + - refunded + - requires_action + fulfillment_status: + type: string + description: The draft order's fulfillment status. + enum: + - canceled + - not_fulfilled + - partially_fulfilled + - fulfilled + - partially_shipped + - shipped + - partially_delivered + - delivered + transactions: + type: array + description: The draft order's transactions. + items: + $ref: ./BaseOrderTransaction.yaml + summary: + $ref: ./BaseOrderSummary.yaml + metadata: + type: object + description: The draft order's metadata, can hold custom key-value pairs. + created_at: + type: string + format: date-time + title: created_at + description: The date the draft order was created. + updated_at: + type: string + format: date-time + title: updated_at + description: The date the draft order was updated. + original_item_total: + type: number + title: original_item_total + description: >- + The total of the draft order's items including taxes, excluding + promotions. + original_item_subtotal: + type: number + title: original_item_subtotal + description: >- + The total of the draft order's items excluding taxes, including + promotions. + original_item_tax_total: + type: number + title: original_item_tax_total + description: The tax total of the draft order's items excluding promotions. + item_total: + type: number + title: item_total + description: The total of the draft order's items including taxes and promotions. + item_subtotal: + type: number + title: item_subtotal + description: >- + The total of the draft order's items excluding taxes, including + promotions. + item_tax_total: + type: number + title: item_tax_total + description: The tax total of the draft order's items including promotions. + original_total: + type: number + title: original_total + description: The draft order's total excluding promotions, including taxes. + original_subtotal: + type: number + title: original_subtotal + description: The draft order's total excluding taxes, including promotions. + original_tax_total: + type: number + title: original_tax_total + description: The draft order's tax total, excluding promotions. + total: + type: number + title: total + description: The draft order's total including taxes and promotions. + subtotal: + type: number + title: subtotal + description: The draft order's total excluding taxes, including promotions. + tax_total: + type: number + title: tax_total + description: The draft order's tax total including promotions. + discount_total: + type: number + title: discount_total + description: The draft order's discount or promotions total. + discount_tax_total: + type: number + title: discount_tax_total + description: The tax total of draft order's discount or promotion. + gift_card_total: + type: number + title: gift_card_total + description: The draft order's gift card total. + gift_card_tax_total: + type: number + title: gift_card_tax_total + description: The tax total of the draft order's gift card. + shipping_total: + type: number + title: shipping_total + description: The draft order's shipping total including taxes and promotions. + shipping_subtotal: + type: number + title: shipping_subtotal + description: The draft order's shipping total excluding taxes, including promotions. + shipping_tax_total: + type: number + title: shipping_tax_total + description: The tax total of the draft order's shipping. + original_shipping_total: + type: number + title: original_shipping_total + description: The draft order's shipping total including taxes, excluding promotions. + original_shipping_subtotal: + type: number + title: original_shipping_subtotal + description: The draft order's shipping total excluding taxes, including promotions. + original_shipping_tax_total: + type: number + title: original_shipping_tax_total + description: The tax total of the draft order's shipping excluding promotions. diff --git a/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrderListResponse.yaml b/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrderListResponse.yaml new file mode 100644 index 0000000000000..1973bfc0ee3bd --- /dev/null +++ b/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrderListResponse.yaml @@ -0,0 +1,26 @@ +type: object +description: The list of draft orders with pagination fields. +x-schemaName: AdminDraftOrderListResponse +required: + - limit + - offset + - count + - draft_orders +properties: + limit: + type: number + title: limit + description: The maximum number of items retrieved. + offset: + type: number + title: offset + description: The number of items skipped before retrieving the returned items. + count: + type: number + title: count + description: The total count of items available. + draft_orders: + type: array + description: The list of draft orders. + items: + $ref: ./AdminDraftOrder.yaml diff --git a/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrderResponse.yaml b/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrderResponse.yaml index e10135e61d015..fa2dd4e372826 100644 --- a/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrderResponse.yaml +++ b/www/apps/api-reference/specs/store/components/schemas/AdminDraftOrderResponse.yaml @@ -5,4 +5,4 @@ required: - draft_order properties: draft_order: - $ref: ./AdminOrder.yaml + $ref: ./AdminDraftOrder.yaml diff --git a/www/apps/api-reference/specs/store/components/schemas/AdminUpdateDraftOrder.yaml b/www/apps/api-reference/specs/store/components/schemas/AdminUpdateDraftOrder.yaml new file mode 100644 index 0000000000000..e324201489280 --- /dev/null +++ b/www/apps/api-reference/specs/store/components/schemas/AdminUpdateDraftOrder.yaml @@ -0,0 +1,108 @@ +type: object +description: The data to update in the draft order. +x-schemaName: AdminUpdateDraftOrder +properties: + email: + type: string + title: email + description: The customer email associated with the draft order. + format: email + shipping_address: + type: object + description: The draft order's shipping address. + properties: + first_name: + type: string + title: first_name + description: The shipping address's first name. + last_name: + type: string + title: last_name + description: The shipping address's last name. + phone: + type: string + title: phone + description: The shipping address's phone. + company: + type: string + title: company + description: The shipping address's company. + address_1: + type: string + title: address_1 + description: The first address line. + address_2: + type: string + title: address_2 + description: The second address line. + city: + type: string + title: city + description: The shipping address's city. + country_code: + type: string + title: country_code + description: The shipping address's country code. + example: us + province: + type: string + title: province + description: The shipping address's province. + postal_code: + type: string + title: postal_code + description: The shipping address's postal code. + metadata: + type: object + description: The shipping address's metadata, can hold custom key-value pairs. + billing_address: + type: object + description: The draft order's billing address. + properties: + first_name: + type: string + title: first_name + description: The billing address's first name. + last_name: + type: string + title: last_name + description: The billing address's last name. + phone: + type: string + title: phone + description: The billing address's phone. + company: + type: string + title: company + description: The billing address's company. + address_1: + type: string + title: address_1 + description: The first address line. + address_2: + type: string + title: address_2 + description: The second address line. + city: + type: string + title: city + description: The billing address's city. + country_code: + type: string + title: country_code + description: The billing address's country code. + example: us + province: + type: string + title: province + description: The billing address's province. + postal_code: + type: string + title: postal_code + description: The billing address's postal code. + metadata: + type: object + description: The billing address's metadata, can hold custom key-value pairs. + metadata: + type: object + description: The draft order's metadata, can hold custom key-value pairs. diff --git a/www/apps/api-reference/specs/store/components/schemas/BaseOrderSummary.yaml b/www/apps/api-reference/specs/store/components/schemas/BaseOrderSummary.yaml index 26db0db078bdd..acc9cf510f48e 100644 --- a/www/apps/api-reference/specs/store/components/schemas/BaseOrderSummary.yaml +++ b/www/apps/api-reference/specs/store/components/schemas/BaseOrderSummary.yaml @@ -2,49 +2,14 @@ type: object description: The order's summary details. x-schemaName: BaseOrderSummary required: - - total - - subtotal - - total_tax - - ordered_total - - fulfilled_total - - returned_total - - return_request_total - - write_off_total + - pending_difference + - current_order_total + - original_order_total + - transaction_total - paid_total - refunded_total + - accounting_total properties: - total: - type: number - title: total - description: The order's total including taxes and promotions. - subtotal: - type: number - title: subtotal - description: The order's total excluding taxes, including promotions. - total_tax: - type: number - title: total_tax - description: The order's total taxes. - ordered_total: - type: number - title: ordered_total - description: The order's total when it was placed. - fulfilled_total: - type: number - title: fulfilled_total - description: The total of the fulfilled items of the order. - returned_total: - type: number - title: returned_total - description: The total of the order's returned items. - return_request_total: - type: number - title: return_request_total - description: The total of the items requested to be returned. - write_off_total: - type: number - title: write_off_total - description: The total of the items removed from the order. paid_total: type: number title: paid_total @@ -53,3 +18,25 @@ properties: type: number title: refunded_total description: The total amount refunded. + pending_difference: + type: number + title: pending_difference + description: >- + The difference pending to be processed. If negative, the customer needs a + refund. Otherwise, additional payment is required from the customer. + current_order_total: + type: number + title: current_order_total + description: The order's current total, could be the total after a change in the order. + original_order_total: + type: number + title: original_order_total + description: The order's original total. + transaction_total: + type: number + title: transaction_total + description: The total of the transactions made on the order. + accounting_total: + type: number + title: accounting_total + description: The order's total without the credit-line total. diff --git a/www/apps/api-reference/specs/store/components/schemas/BasePaymentCollection.yaml b/www/apps/api-reference/specs/store/components/schemas/BasePaymentCollection.yaml index 1dbd0354387dd..e5c542339e375 100644 --- a/www/apps/api-reference/specs/store/components/schemas/BasePaymentCollection.yaml +++ b/www/apps/api-reference/specs/store/components/schemas/BasePaymentCollection.yaml @@ -59,6 +59,8 @@ properties: - awaiting - authorized - partially_authorized + - completed + - failed payment_providers: type: array description: >- diff --git a/www/apps/api-reference/specs/store/components/schemas/BasePaymentSession.yaml b/www/apps/api-reference/specs/store/components/schemas/BasePaymentSession.yaml index 33533c274e3c9..b03530fb1b30a 100644 --- a/www/apps/api-reference/specs/store/components/schemas/BasePaymentSession.yaml +++ b/www/apps/api-reference/specs/store/components/schemas/BasePaymentSession.yaml @@ -1,6 +1,13 @@ type: object description: The payment session's details. x-schemaName: BasePaymentSession +required: + - id + - amount + - currency_code + - provider_id + - data + - status properties: id: type: string @@ -26,7 +33,7 @@ properties: payment. externalDocs: url: >- - https://docs.medusajs.com/v2/resources/commerce-modules/payment/payment-session#data-property + https://docs.medusajs.com/resources/commerce-modules/payment/payment-session#data-property context: type: object description: The context around the payment, such as the customer's details. @@ -37,12 +44,12 @@ properties: type: string description: The payment session's status. enum: + - error - authorized - - captured - canceled + - captured - pending - requires_more - - error authorized_at: type: string title: authorized_at @@ -52,10 +59,3 @@ properties: type: object payment: $ref: ./BasePayment.yaml -required: - - id - - amount - - currency_code - - provider_id - - data - - status diff --git a/www/apps/api-reference/specs/store/components/schemas/OrderTransaction.yaml b/www/apps/api-reference/specs/store/components/schemas/OrderTransaction.yaml index 39323d61139f4..d497d2305cd4f 100644 --- a/www/apps/api-reference/specs/store/components/schemas/OrderTransaction.yaml +++ b/www/apps/api-reference/specs/store/components/schemas/OrderTransaction.yaml @@ -4,6 +4,7 @@ x-schemaName: OrderTransaction required: - id - order_id + - version - order - amount - currency_code @@ -59,3 +60,7 @@ properties: description: The date that the transaction was updated. order: type: object + version: + type: number + title: version + description: The order version that the transaction belongs to. diff --git a/www/apps/api-reference/specs/store/components/schemas/StoreCartShippingOption.yaml b/www/apps/api-reference/specs/store/components/schemas/StoreCartShippingOption.yaml index 77bd8ba167edc..fc89157eaa39a 100644 --- a/www/apps/api-reference/specs/store/components/schemas/StoreCartShippingOption.yaml +++ b/www/apps/api-reference/specs/store/components/schemas/StoreCartShippingOption.yaml @@ -14,6 +14,7 @@ required: - amount - prices - calculated_price + - insufficient_inventory properties: id: type: string @@ -101,3 +102,9 @@ properties: $ref: ./StorePrice.yaml calculated_price: $ref: ./StoreCalculatedPrice.yaml + insufficient_inventory: + type: boolean + title: insufficient_inventory + description: >- + Whether the shipping option's location doesn't have sufficient quantity + for any of the cart's items. diff --git a/www/apps/api-reference/specs/store/openapi.full.yaml b/www/apps/api-reference/specs/store/openapi.full.yaml index d454ad0841b74..ddd829b4d06bb 100644 --- a/www/apps/api-reference/specs/store/openapi.full.yaml +++ b/www/apps/api-reference/specs/store/openapi.full.yaml @@ -9043,7 +9043,6 @@ components: x-schemaName: AdminCreateProduct required: - title - - shipping_profile_id - options properties: title: @@ -10899,6 +10898,277 @@ components: type: boolean title: deleted description: Whether the object was deleted. + AdminDraftOrder: + type: object + description: The draft order's details. + x-schemaName: AdminDraftOrder + required: + - payment_collections + - items + - shipping_methods + - status + - currency_code + - id + - version + - region_id + - customer_id + - sales_channel_id + - email + - payment_status + - fulfillment_status + - summary + - created_at + - updated_at + - original_item_total + - original_item_subtotal + - original_item_tax_total + - item_total + - item_subtotal + - item_tax_total + - original_total + - original_subtotal + - original_tax_total + - total + - subtotal + - tax_total + - discount_total + - discount_tax_total + - gift_card_total + - gift_card_tax_total + - shipping_total + - shipping_subtotal + - shipping_tax_total + - original_shipping_total + - original_shipping_subtotal + - original_shipping_tax_total + properties: + payment_collections: + type: array + description: The draft order's payment collections. + items: + $ref: '#/components/schemas/AdminPaymentCollection' + fulfillments: + type: array + description: The draft order's fulfillments. + items: + $ref: '#/components/schemas/AdminOrderFulfillment' + sales_channel: + $ref: '#/components/schemas/AdminSalesChannel' + customer: + $ref: '#/components/schemas/AdminCustomer' + shipping_address: + $ref: '#/components/schemas/AdminOrderAddress' + billing_address: + $ref: '#/components/schemas/AdminOrderAddress' + items: + type: array + description: The draft order's items. + items: + $ref: '#/components/schemas/AdminOrderLineItem' + shipping_methods: + type: array + description: The draft order's shipping methods. + items: + $ref: '#/components/schemas/AdminOrderShippingMethod' + status: + type: string + title: status + description: The draft order's status. + currency_code: + type: string + title: currency_code + description: The draft order's currency code. + example: usd + id: + type: string + title: id + description: The draft order's ID. + version: + type: number + title: version + description: The draft order's version. + region_id: + type: string + title: region_id + description: The ID of the region associated with the draft order. + customer_id: + type: string + title: customer_id + description: The ID of the customer that the draft order belongs to. + sales_channel_id: + type: string + title: sales_channel_id + description: The ID of the sales channel that the draft order is placed in. + email: + type: string + title: email + description: The customer email associated with the draft order. + format: email + display_id: + type: number + title: display_id + description: The draft order's display ID. + payment_status: + type: string + description: The draft order's payment status. + enum: + - not_paid + - awaiting + - authorized + - partially_authorized + - canceled + - captured + - partially_captured + - partially_refunded + - refunded + - requires_action + fulfillment_status: + type: string + description: The draft order's fulfillment status. + enum: + - canceled + - not_fulfilled + - partially_fulfilled + - fulfilled + - partially_shipped + - shipped + - partially_delivered + - delivered + transactions: + type: array + description: The draft order's transactions. + items: + $ref: '#/components/schemas/BaseOrderTransaction' + summary: + $ref: '#/components/schemas/BaseOrderSummary' + metadata: + type: object + description: The draft order's metadata, can hold custom key-value pairs. + created_at: + type: string + format: date-time + title: created_at + description: The date the draft order was created. + updated_at: + type: string + format: date-time + title: updated_at + description: The date the draft order was updated. + original_item_total: + type: number + title: original_item_total + description: The total of the draft order's items including taxes, excluding promotions. + original_item_subtotal: + type: number + title: original_item_subtotal + description: The total of the draft order's items excluding taxes, including promotions. + original_item_tax_total: + type: number + title: original_item_tax_total + description: The tax total of the draft order's items excluding promotions. + item_total: + type: number + title: item_total + description: The total of the draft order's items including taxes and promotions. + item_subtotal: + type: number + title: item_subtotal + description: The total of the draft order's items excluding taxes, including promotions. + item_tax_total: + type: number + title: item_tax_total + description: The tax total of the draft order's items including promotions. + original_total: + type: number + title: original_total + description: The draft order's total excluding promotions, including taxes. + original_subtotal: + type: number + title: original_subtotal + description: The draft order's total excluding taxes, including promotions. + original_tax_total: + type: number + title: original_tax_total + description: The draft order's tax total, excluding promotions. + total: + type: number + title: total + description: The draft order's total including taxes and promotions. + subtotal: + type: number + title: subtotal + description: The draft order's total excluding taxes, including promotions. + tax_total: + type: number + title: tax_total + description: The draft order's tax total including promotions. + discount_total: + type: number + title: discount_total + description: The draft order's discount or promotions total. + discount_tax_total: + type: number + title: discount_tax_total + description: The tax total of draft order's discount or promotion. + gift_card_total: + type: number + title: gift_card_total + description: The draft order's gift card total. + gift_card_tax_total: + type: number + title: gift_card_tax_total + description: The tax total of the draft order's gift card. + shipping_total: + type: number + title: shipping_total + description: The draft order's shipping total including taxes and promotions. + shipping_subtotal: + type: number + title: shipping_subtotal + description: The draft order's shipping total excluding taxes, including promotions. + shipping_tax_total: + type: number + title: shipping_tax_total + description: The tax total of the draft order's shipping. + original_shipping_total: + type: number + title: original_shipping_total + description: The draft order's shipping total including taxes, excluding promotions. + original_shipping_subtotal: + type: number + title: original_shipping_subtotal + description: The draft order's shipping total excluding taxes, including promotions. + original_shipping_tax_total: + type: number + title: original_shipping_tax_total + description: The tax total of the draft order's shipping excluding promotions. + AdminDraftOrderListResponse: + type: object + description: The list of draft orders with pagination fields. + x-schemaName: AdminDraftOrderListResponse + required: + - limit + - offset + - count + - draft_orders + properties: + limit: + type: number + title: limit + description: The maximum number of items retrieved. + offset: + type: number + title: offset + description: The number of items skipped before retrieving the returned items. + count: + type: number + title: count + description: The total count of items available. + draft_orders: + type: array + description: The list of draft orders. + items: + $ref: '#/components/schemas/AdminDraftOrder' AdminDraftOrderResponse: type: object description: The draft order's details. @@ -10907,7 +11177,7 @@ components: - draft_order properties: draft_order: - $ref: '#/components/schemas/AdminOrder' + $ref: '#/components/schemas/AdminDraftOrder' AdminExchange: type: object description: The exchange's details. @@ -18001,6 +18271,115 @@ components: metadata: type: object description: The customer group's metadata, can hold custom key-value pairs. + AdminUpdateDraftOrder: + type: object + description: The data to update in the draft order. + x-schemaName: AdminUpdateDraftOrder + properties: + email: + type: string + title: email + description: The customer email associated with the draft order. + format: email + shipping_address: + type: object + description: The draft order's shipping address. + properties: + first_name: + type: string + title: first_name + description: The shipping address's first name. + last_name: + type: string + title: last_name + description: The shipping address's last name. + phone: + type: string + title: phone + description: The shipping address's phone. + company: + type: string + title: company + description: The shipping address's company. + address_1: + type: string + title: address_1 + description: The first address line. + address_2: + type: string + title: address_2 + description: The second address line. + city: + type: string + title: city + description: The shipping address's city. + country_code: + type: string + title: country_code + description: The shipping address's country code. + example: us + province: + type: string + title: province + description: The shipping address's province. + postal_code: + type: string + title: postal_code + description: The shipping address's postal code. + metadata: + type: object + description: The shipping address's metadata, can hold custom key-value pairs. + billing_address: + type: object + description: The draft order's billing address. + properties: + first_name: + type: string + title: first_name + description: The billing address's first name. + last_name: + type: string + title: last_name + description: The billing address's last name. + phone: + type: string + title: phone + description: The billing address's phone. + company: + type: string + title: company + description: The billing address's company. + address_1: + type: string + title: address_1 + description: The first address line. + address_2: + type: string + title: address_2 + description: The second address line. + city: + type: string + title: city + description: The billing address's city. + country_code: + type: string + title: country_code + description: The billing address's country code. + example: us + province: + type: string + title: province + description: The billing address's province. + postal_code: + type: string + title: postal_code + description: The billing address's postal code. + metadata: + type: object + description: The billing address's metadata, can hold custom key-value pairs. + metadata: + type: object + description: The draft order's metadata, can hold custom key-value pairs. AdminUpdateOrder: type: object description: The details to update in the order. @@ -21156,49 +21535,14 @@ components: description: The order's summary details. x-schemaName: BaseOrderSummary required: - - total - - subtotal - - total_tax - - ordered_total - - fulfilled_total - - returned_total - - return_request_total - - write_off_total + - pending_difference + - current_order_total + - original_order_total + - transaction_total - paid_total - refunded_total + - accounting_total properties: - total: - type: number - title: total - description: The order's total including taxes and promotions. - subtotal: - type: number - title: subtotal - description: The order's total excluding taxes, including promotions. - total_tax: - type: number - title: total_tax - description: The order's total taxes. - ordered_total: - type: number - title: ordered_total - description: The order's total when it was placed. - fulfilled_total: - type: number - title: fulfilled_total - description: The total of the fulfilled items of the order. - returned_total: - type: number - title: returned_total - description: The total of the order's returned items. - return_request_total: - type: number - title: return_request_total - description: The total of the items requested to be returned. - write_off_total: - type: number - title: write_off_total - description: The total of the items removed from the order. paid_total: type: number title: paid_total @@ -21207,6 +21551,26 @@ components: type: number title: refunded_total description: The total amount refunded. + pending_difference: + type: number + title: pending_difference + description: The difference pending to be processed. If negative, the customer needs a refund. Otherwise, additional payment is required from the customer. + current_order_total: + type: number + title: current_order_total + description: The order's current total, could be the total after a change in the order. + original_order_total: + type: number + title: original_order_total + description: The order's original total. + transaction_total: + type: number + title: transaction_total + description: The total of the transactions made on the order. + accounting_total: + type: number + title: accounting_total + description: The order's total without the credit-line total. BaseOrderTransaction: type: object description: An order transaction's details. @@ -21402,6 +21766,8 @@ components: - awaiting - authorized - partially_authorized + - completed + - failed payment_providers: type: array description: The payment provider used to process the collection's payments and sessions. @@ -21432,6 +21798,13 @@ components: type: object description: The payment session's details. x-schemaName: BasePaymentSession + required: + - id + - amount + - currency_code + - provider_id + - data + - status properties: id: type: string @@ -21454,7 +21827,7 @@ components: type: object description: The payment session's data, useful for the payment provider processing the payment. externalDocs: - url: https://docs.medusajs.com/v2/resources/commerce-modules/payment/payment-session#data-property + url: https://docs.medusajs.com/resources/commerce-modules/payment/payment-session#data-property context: type: object description: The context around the payment, such as the customer's details. @@ -21465,12 +21838,12 @@ components: type: string description: The payment session's status. enum: + - error - authorized - - captured - canceled + - captured - pending - requires_more - - error authorized_at: type: string title: authorized_at @@ -21480,13 +21853,6 @@ components: type: object payment: $ref: '#/components/schemas/BasePayment' - required: - - id - - amount - - currency_code - - provider_id - - data - - status BaseProduct: type: object description: The product's details. @@ -24395,6 +24761,7 @@ components: required: - id - order_id + - version - order - amount - currency_code @@ -24447,6 +24814,10 @@ components: description: The date that the transaction was updated. order: type: object + version: + type: number + title: version + description: The order version that the transaction belongs to. RefundReason: type: object description: The refund reason's details. @@ -25679,6 +26050,7 @@ components: - amount - prices - calculated_price + - insufficient_inventory properties: id: type: string @@ -25762,6 +26134,10 @@ components: $ref: '#/components/schemas/StorePrice' calculated_price: $ref: '#/components/schemas/StoreCalculatedPrice' + insufficient_inventory: + type: boolean + title: insufficient_inventory + description: Whether the shipping option's location doesn't have sufficient quantity for any of the cart's items. StoreCollection: type: object description: The collection's details. diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index fb2af3288ef4f..0dabfa454430c 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -187,6 +187,16 @@ Per Vercel’s [license and plans](https://vercel.com/pricing), their free plan Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for specific hosting providers. +# Debugging and Testing + +In the next chapters, you’ll learn about the tools Medusa provides for testing and debugging your Medusa application. + +By the end of this chapter, you’ll learn: + +- How to use Medusa's `@medusajs/test-utils` test to write integration tests. +- How to use Medusa’s `Logger` utility to log messages. + + # Install Medusa In this chapter, you'll learn how to install and run a Medusa application. @@ -318,38 +328,6 @@ The Development Resources documentation provides guides and references that are Check out the Development Resources documentation [here](https://docs.medusajs.com/resources/index.html.md). -# Storefront Development - -The Medusa application is made up of a Node.js server and an admin dashboard. Storefronts are installed, built, and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience. - -You can build your storefront from scratch with your preferred tech stack, or start with our Next.js Starter storefront. The Next.js Starter storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience. - -- [Install Next.js Starter Storefront](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) -- [Build Custom Storefront](https://docs.medusajs.com/resources/storefront-development/index.html.md) - -*** - -## Passing a Publishable API Key in Storefront Requests - -When sending a request to an API route starting with `/store`, you must include a publishable API key in the header of your request. - -A publishable API key sets the scope of your request to one or more sales channels. - -Then, when you retrieve products, only products of those sales channels are retrieved. This also ensures you retrieve correct inventory data, and associate created orders with the scoped sales channel. - -Learn more about passing the publishable API key in [this storefront development guide](https://docs.medusajs.com/resources/storefront-development/publishable-api-keys/index.html.md). - - -# Debugging and Testing - -In the next chapters, you’ll learn about the tools Medusa provides for testing and debugging your Medusa application. - -By the end of this chapter, you’ll learn: - -- How to use Medusa's `@medusajs/test-utils` test to write integration tests. -- How to use Medusa’s `Logger` utility to log messages. - - # Updating Medusa In this chapter, you'll learn about updating your Medusa application and packages. @@ -456,6 +434,28 @@ npm install ``` +# Storefront Development + +The Medusa application is made up of a Node.js server and an admin dashboard. Storefronts are installed, built, and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience. + +You can build your storefront from scratch with your preferred tech stack, or start with our Next.js Starter storefront. The Next.js Starter storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience. + +- [Install Next.js Starter Storefront](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) +- [Build Custom Storefront](https://docs.medusajs.com/resources/storefront-development/index.html.md) + +*** + +## Passing a Publishable API Key in Storefront Requests + +When sending a request to an API route starting with `/store`, you must include a publishable API key in the header of your request. + +A publishable API key sets the scope of your request to one or more sales channels. + +Then, when you retrieve products, only products of those sales channels are retrieved. This also ensures you retrieve correct inventory data, and associate created orders with the scoped sales channel. + +Learn more about passing the publishable API key in [this storefront development guide](https://docs.medusajs.com/resources/storefront-development/publishable-api-keys/index.html.md). + + # Using TypeScript Aliases By default, Medusa doesn't support TypeScript aliases in production. @@ -500,114 +500,6 @@ import { BrandModuleService } from "@/modules/brand/service" ``` -# Build Custom Features - -In the upcoming chapters, you'll follow step-by-step guides to build custom features in Medusa. These guides gradually introduce Medusa's concepts to help you understand what they are and how to use them. - -By following these guides, you'll add brands to the Medusa application that you can associate with products. - -To build a custom feature in Medusa, you need three main tools: - -- [Module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md): a package with commerce logic for a single domain. It defines new tables to add to the database, and a class of methods to manage these tables. -- [Workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md): a tool to perform an operation comprising multiple steps with built-in rollback and retry mechanisms. -- [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md): a REST endpoint that exposes commerce features to clients, such as the admin dashboard or a storefront. The API route executes a workflow that implements the commerce feature using modules. - -![Diagram showcasing the flow of a custom developed feature](https://res.cloudinary.com/dza7lstvk/image/upload/v1725867628/Medusa%20Book/custom-development_nofvp6.jpg) - -*** - -## Next Chapters: Brand Module Example - -The next chapters will guide you to: - -1. Build a Brand Module that creates a `Brand` data model and provides data-management features. -2. Add a workflow to create a brand. -3. Expose an API route that allows admin users to create a brand using the workflow. - - -# Customize Medusa Admin Dashboard - -In the previous chapters, you've customized your Medusa application to [add brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), [expose an API route to create brands](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), and [linked brands to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md). - -After customizing and extending your application with new features, you may need to provide an interface for admin users to utilize these features. The Medusa Admin dashboard is extendable, allowing you to: - -- Insert components, called [widgets](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md), on existing pages. -- Add new pages, called [UI Routes](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md). - -From these customizations, you can send requests to custom API routes, allowing admin users to manage custom resources on the dashboard - -*** - -## Next Chapters: View Brands in Dashboard - -In the next chapters, you'll continue with the brands example to: - -- Add a new section to the product details page that shows the product's brand. -- Add a new page in the dashboard that shows all brands in the store. - - -# Extend Core Commerce Features - -In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features. - -In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run. - -Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs: - -- [Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects. -- [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds. -- [Additional Data in API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow. - -*** - -## Next Chapters: Link Brands to Products Example - -The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](https://docs.medusajs.com/learn/customization/custom-features/index.html.md) to: - -- Link brands from the custom [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to products from Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md). -- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product. -- Retrieve a product's associated brand's details. - - -# Customizations Next Steps: Learn the Fundamentals - -The previous guides introduced Medusa's different concepts and how you can use them to customize Medusa for a realistic use case, You added brands to your application, linked them to products, customized the admin dashboard, and integrated a third-party CMS. - -The next chapters will cover each of these concepts in depth, with the different ways you can use them, their options or configurations, and more advanced features that weren't covered in the previous guides. While you can start building with Medusa, it's highly recommended to follow the next chapters for a better understanding of Medusa's fundamentals. - -## Helpful Resources Guides - -The [Development Resources](https://docs.medusajs.com/resources/index.html.md) documentation provides more helpful guides and references for your development journey. Some of these guides and references include: - -3. [Commerce Modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md): Browse the list of commerce modules in Medusa and their references to learn how to use them. -4. [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md): Learn about the methods generated by `MedusaService` with examples. -5. [Workflows Reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md): Browse the list of core workflows and their hooks that are useful for your customizations. -6. [Admin Injection Zones](https://docs.medusajs.com/resources/admin-widget-injection-zones/index.html.md): Browse the injection zones in the Medusa Admin to learn where you can inject widgets. - -*** - -## More Examples in Recipes - -In the Development Resources documentation, you'll also find step-by-step guides for different use cases, such as building a marketplace, digital products, and more. - -Refer to the [Recipes](https://docs.medusajs.com/resources/recipes/index.html.md) documentation to learn more. - - -# Re-Use Customizations with Plugins - -In the previous chapters, you've learned important concepts related to creating modules, implementing commerce features in workflows, exposing those features in API routes, customizing the Medusa Admin dashboard with Admin Extensions, and integrating third-party systems. - -You've implemented the brands example within a single Medusa application. However, this approach is not scalable when you want to reuse your customizations across multiple projects. - -To reuse your customizations across multiple Medusa applications, such as implementing brands in different projects, you can create a plugin. A plugin is an NPM package that encapsulates your customizations and can be installed in any Medusa application. Plugins can include modules, workflows, API routes, Admin Extensions, and more. - -![Diagram showcasing how the Brand Plugin would add its resources to any application it's installed in](https://res.cloudinary.com/dza7lstvk/image/upload/v1737540091/Medusa%20Book/brand-plugin_bk9zi9.jpg) - -Medusa provides the tooling to create a plugin package, test it in a local Medusa application, and publish it to NPM. - -To learn more about plugins and how to create them, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). - - # General Medusa Application Deployment Guide In this document, you'll learn the general steps to deploy your Medusa application. How you apply these steps depend on your chosen hosting provider or platform. @@ -909,1042 +801,1102 @@ Replace the email `admin-medusa@test.com` and password `supersecret` with the cr You can use these credentials to log into the Medusa Admin dashboard. -# Integrate Third-Party Systems +# Configure Instrumentation -Commerce applications often connect to third-party systems that provide additional or specialized features. For example, you may integrate a Content-Management System (CMS) for rich content features, a payment provider to process credit-card payments, and a notification service to send emails. +In this chapter, you'll learn about observability in Medusa and how to configure instrumentation with OpenTelemetry. -Medusa's framework facilitates integrating these systems and orchestrating operations across them, saving you the effort of managing them yourself. You won't find those capabilities in other commerce platforms that in these scenarios become a bottleneck to building customizations and iterating quickly. +## Observability with OpenTelemtry -In Medusa, you integrate a third-party system by: +Medusa uses [OpenTelemetry](https://opentelemetry.io/) for instrumentation and reporting. When configured, it reports traces for: -1. Creating a module whose service provides the methods to connect to and perform operations in the third-party system. -2. Building workflows that complete tasks spanning across systems. You use the module that integrates a third-party system in the workflow's steps. -3. Executing the workflows you built in an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), at a scheduled time, or when an event is emitted. +- HTTP requests +- Workflow executions +- Query usages +- Database queries and operations *** -## Next Chapters: Sync Brands Example +## How to Configure Instrumentation in Medusa? -In the previous chapters, you've [added brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to your Medusa application. In the next chapters, you will: +### Prerequisites -1. Integrate a dummy third-party CMS in the Brand Module. -2. Sync brands to the CMS when a brand is created. -3. Sync brands from the CMS at a daily schedule. +- [An exporter to visualize your application's traces, such as Zipkin.](https://zipkin.io/pages/quickstart.html) +### Install Dependencies -# Admin Development +Start by installing the following OpenTelemetry dependencies in your Medusa project: -In the next chapters, you'll learn more about possible admin customizations. +```bash npm2yarn +npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg +``` -You can customize the admin dashboard by: +Also, install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies: -- Adding new sections to existing pages using Widgets. -- Adding new pages using UI Routes. +```bash npm2yarn +npm install @opentelemetry/exporter-zipkin +``` -*** +### Add instrumentation.ts -## Medusa UI Package +Next, create the file `instrumentation.ts` with the following content: -Medusa provides a Medusa UI package to facilitate your admin development through ready-made components and ensure a consistent design between your customizations and the dashboard’s design. +```ts title="instrumentation.ts" +import { registerOtel } from "@medusajs/medusa" +import { ZipkinExporter } from "@opentelemetry/exporter-zipkin" -Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.md) to learn how to install it and use its components. +// If using an exporter other than Zipkin, initialize it here. +const exporter = new ZipkinExporter({ + serviceName: "my-medusa-project", +}) -*** +export function register() { + registerOtel({ + serviceName: "medusajs", + // pass exporter + exporter, + instrument: { + http: true, + workflows: true, + query: true, + }, + }) +} +``` -## Admin Components List +In the `instrumentation.ts` file, you export a `register` function that uses Medusa's `registerOtel` utility function. You also initialize an instance of the exporter, such as Zipkin, and pass it to the `registerOtel` function. -To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components. +`registerOtel` accepts an object where you can pass any [NodeSDKConfiguration](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_sdk_node.NodeSDKConfiguration.html) property along with the following properties: +The `NodeSDKConfiguration` properties are accepted since Medusa v2.5.1. -# Custom CLI Scripts +- serviceName: (\`string\`) The name of the service traced. +- exporter: (\[SpanExporter]\(https://open-telemetry.github.io/opentelemetry-js/interfaces/\_opentelemetry\_sdk\_trace\_base.SpanExporter.html)) An instance of an exporter, such as Zipkin. +- instrument: (\`object\`) Options specifying what to trace. -In this chapter, you'll learn how to create and execute custom scripts from Medusa's CLI tool. + - http: (\`boolean\`) Whether to trace HTTP requests. -## What is a Custom CLI Script? + - query: (\`boolean\`) Whether to trace Query usages. -A custom CLI script is a function to execute through Medusa's CLI tool. This is useful when creating custom Medusa tooling to run through the CLI. + - workflows: (\`boolean\`) Whether to trace Workflow executions. + + - db: (\`boolean\`) Whether to trace database queries and operations. +- instrumentations: (\[Instrumentation\[]]\(https://open-telemetry.github.io/opentelemetry-js/interfaces/\_opentelemetry\_instrumentation.Instrumentation.html)) Additional instrumentation options that OpenTelemetry accepts. *** -## How to Create a Custom CLI Script? +## Test it Out -To create a custom CLI script, create a TypeScript or JavaScript file under the `src/scripts` directory. The file must default export a function. - -For example, create the file `src/scripts/my-script.ts` with the following content: - -```ts title="src/scripts/my-script.ts" -import { - ExecArgs, - IProductModuleService, -} from "@medusajs/framework/types" -import { Modules } from "@medusajs/framework/utils" - -export default async function myScript({ container }: ExecArgs) { - const productModuleService: IProductModuleService = container.resolve( - Modules.PRODUCT - ) - - const [, count] = await productModuleService - .listAndCountProducts() - - console.log(`You have ${count} product(s)`) -} -``` - -The function receives as a parameter an object having a `container` property, which is an instance of the Medusa Container. Use it to resolve resources in your Medusa application. - -*** - -## How to Run Custom CLI Script? +To test it out, start your exporter, such as Zipkin. -To run the custom CLI script, run the Medusa CLI's `exec` command: +Then, start your Medusa application: -```bash -npx medusa exec ./src/scripts/my-script.ts +```bash npm2yarn +npm run dev ``` -*** - -## Custom CLI Script Arguments - -Your script can accept arguments from the command line. Arguments are passed to the function's object parameter in the `args` property. - -For example: +Try to open the Medusa Admin or send a request to an API route. -```ts -import { ExecArgs } from "@medusajs/framework/types" +If you check traces in your exporter, you'll find new traces reported. -export default async function myScript({ args }: ExecArgs) { - console.log(`The arguments you passed: ${args}`) -} -``` +### Trace Span Names -Then, pass the arguments in the `exec` command after the file path: +Trace span names start with the following keywords based on what it's reporting: -```bash -npx medusa exec ./src/scripts/my-script.ts arg1 arg2 -``` +- `{methodName} {URL}` when reporting HTTP requests, where `{methodName}` is the HTTP method, and `{URL}` is the URL the request is sent to. +- `route:` when reporting route handlers running on an HTTP request. +- `middleware:` when reporting a middleware running on an HTTP request. +- `workflow:` when reporting a workflow execution. +- `step:` when reporting a step in a workflow execution. +- `query.graph:` when reporting Query usages. +- `pg.query:` when reporting database queries and operations. -# API Routes +# Logging -In this chapter, you’ll learn what API Routes are and how to create them. +In this chapter, you’ll learn how to use Medusa’s logging utility. -## What is an API Route? +## Logger Class -An API Route is an endpoint. It exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems. +Medusa provides a `Logger` class with advanced logging functionalities. This includes configuring logging levels or saving logs to a file. -The Medusa core application provides a set of admin and store API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities. +The Medusa application registers the `Logger` class in the Medusa container and each module's container as `logger`. *** -## How to Create an API Route? +## How to Log a Message -An API Route is created in a TypeScript or JavaScript file under the `src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`. +Resolve the `logger` using the Medusa container to log a message in your resource. -![Example of API route in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732808645/Medusa%20Book/route-dir-overview_dqgzmk.jpg) +For example, create the file `src/jobs/log-message.ts` with the following content: -Each file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…). +```ts title="src/jobs/log-message.ts" highlights={highlights} +import { MedusaContainer } from "@medusajs/framework/types" +import { ContainerRegistrationKeys } from "@medusajs/framework/utils" -For example, to create a `GET` API Route at `/hello-world`, create the file `src/api/hello-world/route.ts` with the following content: +export default async function myCustomJob( + container: MedusaContainer +) { + const logger = container.resolve(ContainerRegistrationKeys.LOGGER) -```ts title="src/api/hello-world/route.ts" -import type { - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" + logger.info("I'm using the logger!") +} -export const GET = ( - req: MedusaRequest, - res: MedusaResponse -) => { - res.json({ - message: "[GET] Hello world!", - }) +export const config = { + name: "test-logger", + // execute every minute + schedule: "* * * * *", } ``` -### Test API Route +This creates a scheduled job that resolves the `logger` from the Medusa container and uses it to log a message. -To test the API route above, start the Medusa application: +### Test the Scheduled Job + +To test out the above scheduled job, start the Medusa application: ```bash npm2yarn npm run dev ``` -Then, send a `GET` request to the `/hello-world` API Route: +After a minute, you'll see the following message as part of the logged messages: -```bash -curl http://localhost:9000/hello-world +```text +info: I'm using the logger! ``` *** -## When to Use API Routes +## Log Levels -You're exposing custom functionality to be used by a storefront, admin dashboard, or any external application. +The `Logger` class has the following methods: +- `info`: The message is logged with level `info`. +- `warn`: The message is logged with level `warn`. +- `error`: The message is logged with level `error`. +- `debug`: The message is logged with level `debug`. -# Environment Variables +Each of these methods accepts a string parameter to log in the terminal with the associated level. -In this chapter, you'll learn how environment variables are loaded in Medusa. +*** -## System Environment Variables +## Logging Configurations -The Medusa application loads and uses system environment variables. +### Log Level -For example, if you set the `PORT` environment variable to `8000`, the Medusa application runs on that port instead of `9000`. +The available log levels, from lowest to highest levels, are: -In production, you should always use system environment variables that you set through your hosting provider. +1. `silly` (default, meaning messages of all levels are logged) +2. `debug` +3. `info` +4. `warn` +5. `error` -*** +You can change that by setting the `LOG_LEVEL` environment variable to the minimum level you want to be logged. -## Environment Variables in .env Files +For example: -During development, it's easier to set environment variables in a `.env` file in your repository. +```bash +LOG_LEVEL=error +``` -Based on your `NODE_ENV` system environment variable, Medusa will try to load environment variables from the following `.env` files: +This logs `error` messages only. -As of [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0), `NODE_ENV` defaults to `production` when using `medusa start`. Otherwise, it defaults to `development`. +The environment variable must be set as a system environment variable and not in `.env`. -|\`.env\`| -|---|---| -|\`NODE\_ENV\`|\`.env\`| -|\`NODE\_ENV\`|\`.env.production\`| -|\`NODE\_ENV\`|\`.env.staging\`| -|\`NODE\_ENV\`|\`.env.test\`| +### Save Logs in a File -### Set Environment in `loadEnv` +Aside from showing the logs in the terminal, you can save the logs in a file by setting the `LOG_FILE` environment variable to the path of the file relative to the Medusa server’s root directory. -In the `medusa-config.ts` file of your Medusa application, you'll find a `loadEnv` function used that accepts `process.env.NODE_ENV` as a first parameter. +For example: -This function is responsible for loading the correct `.env` file based on the value of `process.env.NODE_ENV`. +```bash +LOG_FILE=all.log +``` -To ensure that the correct `.env` file is loaded as shown in the table above, only specify `development`, `production`, `staging` or `test` as the value of `process.env.NODE_ENV` or as the parameter of `loadEnv`. +Your logs are now saved in the `all.log` file at the root of your Medusa application. + +The environment variable must be set as a system environment variable and not in `.env`. *** -## Environment Variables for Admin Customizations +## Show Log with Progress -Since the Medusa Admin is built on top of [Vite](https://vite.dev/), you prefix the environment variables you want to use in a widget or UI route with `VITE_`. Then, you can access or use them with the `import.meta.env` object. +The `Logger` class has an `activity` method used to log a message of level `info`. If the Medusa application is running in a development environment, a spinner starts to show the activity's progress. -Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md). +For example: -*** +```ts title="src/jobs/log-message.ts" +import { MedusaContainer } from "@medusajs/framework/types" +import { ContainerRegistrationKeys } from "@medusajs/framework/utils" -## Predefined Medusa Environment Variables +export default async function myCustomJob( + container: MedusaContainer +) { + const logger = container.resolve(ContainerRegistrationKeys.LOGGER) -The Medusa application uses the following predefined environment variables that you can set: + const activityId = logger.activity("First log message") -You should opt for setting configurations in `medusa-config.ts` where possible. For a full list of Medusa configurations, refer to [this documenation](https://docs.medusajs.com/resources/references/medusa-config/index.html.md). + logger.progress(activityId, `Second log message`) -|Environment Variable|Description|Default| -|---|---|---|---|---| -| -| -| -| -||The URL to connect to the PostgreSQL database. Only used if || -||URLs of storefronts that can access the Medusa backend's Store APIs. Only used if || -||URLs of admin dashboards that can access the Medusa backend's Admin APIs. Only used if || -||URLs of clients that can access the Medusa backend's authentication routes. Only used if || -||A random string used to create authentication tokens in the http layer. Only used if || -||A random string used to create cookie tokens in the http layer. Only used if || -||The URL to the Medusa backend. Only used if || -| -| -| -| -| -| -| -| -||The allowed levels to log. Learn more in || -||The file to save logs in. By default, logs aren't saved in any file. Learn more in || -||Whether to disable analytics data collection. Learn more in || + logger.success(activityId, "Last log message") +} +``` +The `activity` method returns the ID of the started activity. This ID can then be passed to one of the following methods of the `Logger` class: -# Events and Subscribers +- `progress`: Log a message of level `info` that indicates progress within that same activity. +- `success`: Log a message of level `info` that indicates that the activity has succeeded. This also ends the associated activity. +- `failure`: Log a message of level `error` that indicates that the activity has failed. This also ends the associated activity. -In this chapter, you’ll learn about Medusa's event system, and how to handle events with subscribers. +If you configured the `LOG_LEVEL` environment variable to a level higher than those associated with the above methods, their messages won’t be logged. -## Handle Core Commerce Flows with Events -When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order, or syncing data that's updated in Medusa to a third-party system. +# Build Custom Features -Medusa emits events when core commerce features are performed, and you can listen to and handle these events in asynchronous functions. You can think of Medusa's events like you'd think about webhooks in other commerce platforms, but instead of having to setup separate applications to handle webhooks, your efforts only go into writing the logic right in your Medusa codebase. +In the upcoming chapters, you'll follow step-by-step guides to build custom features in Medusa. These guides gradually introduce Medusa's concepts to help you understand what they are and how to use them. -You listen to an event in a subscriber, which is an asynchronous function that's executed when its associated event is emitted. +By following these guides, you'll add brands to the Medusa application that you can associate with products. -![A diagram showcasing an example of how an event is emitted when an order is placed.](https://res.cloudinary.com/dza7lstvk/image/upload/v1732277948/Medusa%20Book/order-placed-event-example_e4e4kw.jpg) +To build a custom feature in Medusa, you need three main tools: -Subscribers are useful to perform actions that aren't integral to the original flow. For example, you can handle the `order.placed` event in a subscriber that sends a confirmation email to the customer. The subscriber has no impact on the original order-placement flow, as it's executed outside of it. +- [Module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md): a package with commerce logic for a single domain. It defines new tables to add to the database, and a class of methods to manage these tables. +- [Workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md): a tool to perform an operation comprising multiple steps with built-in rollback and retry mechanisms. +- [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md): a REST endpoint that exposes commerce features to clients, such as the admin dashboard or a storefront. The API route executes a workflow that implements the commerce feature using modules. -If the action you're performing is integral to the main flow of the core commerce feature, use [workflow hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) instead. +![Diagram showcasing the flow of a custom developed feature](https://res.cloudinary.com/dza7lstvk/image/upload/v1725867628/Medusa%20Book/custom-development_nofvp6.jpg) -### List of Emitted Events +*** -Find a list of all emitted events in [this reference](https://docs.medusajs.com/resources/events-reference/index.html.md). +## Next Chapters: Brand Module Example -*** +The next chapters will guide you to: -## How to Create a Subscriber? +1. Build a Brand Module that creates a `Brand` data model and provides data-management features. +2. Add a workflow to create a brand. +3. Expose an API route that allows admin users to create a brand using the workflow. -You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. The file exports the function to execute and the subscriber's configuration that indicate what event(s) it listens to. -For example, create the file `src/subscribers/order-placed.ts` with the following content: +# Medusa Testing Tools -![Example of subscriber file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732866244/Medusa%20Book/subscriber-dir-overview_pusyeu.jpg) +In this chapter, you'll learn about Medusa's testing tools and how to install and configure them. -```ts title="src/subscribers/product-created.ts" -import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" -import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation" +## @medusajs/test-utils Package -export default async function orderPlacedHandler({ - event: { data }, - container, -}: SubscriberArgs<{ id: string }>) { - const logger = container.resolve("logger") +Medusa provides a Testing Framework to create integration tests for your custom API routes, modules, or other Medusa customizations. - logger.info("Sending confirmation email...") +To use the Testing Framework, install `@medusajs/test-utils` as a `devDependency`: - await sendOrderConfirmationWorkflow(container) - .run({ - input: { - id: data.id, +```bash npm2yarn +npm install --save-dev @medusajs/test-utils@latest +``` + +*** + +## Install and Configure Jest + +Writing tests with `@medusajs/test-utils`'s tools requires installing and configuring Jest in your project. + +Run the following command to install the required Jest dependencies: + +```bash npm2yarn +npm install --save-dev jest @types/jest @swc/jest +``` + +Then, create the file `jest.config.js` with the following content: + +```js title="jest.config.js" +const { loadEnv } = require("@medusajs/framework/utils") +loadEnv("test", process.cwd()) + +module.exports = { + transform: { + "^.+\\.[jt]s$": [ + "@swc/jest", + { + jsc: { + parser: { syntax: "typescript", decorators: true }, + }, }, - }) + ], + }, + testEnvironment: "node", + moduleFileExtensions: ["js", "ts", "json"], + modulePathIgnorePatterns: ["dist/"], + setupFiles: ["./integration-tests/setup.js"], } -export const config: SubscriberConfig = { - event: `order.placed`, +if (process.env.TEST_TYPE === "integration:http") { + module.exports.testMatch = ["**/integration-tests/http/*.spec.[jt]s"] +} else if (process.env.TEST_TYPE === "integration:modules") { + module.exports.testMatch = ["**/src/modules/*/__tests__/**/*.[jt]s"] +} else if (process.env.TEST_TYPE === "unit") { + module.exports.testMatch = ["**/src/**/__tests__/**/*.unit.spec.[jt]s"] } ``` -This subscriber file exports: - -- An asynchronous subscriber function that's executed whenever the associated event, which is `order.placed` is triggered. -- A configuration object with an `event` property whose value is the event the subscriber is listening to. You can also pass an array of event names to listen to multiple events in the same subscriber. - -The subscriber function receives an object as a parameter that has the following properties: +Next, create the `integration-tests/setup.js` file with the following content: -- `event`: An object with the event's details. The `data` property contains the data payload of the event emitted, which is the order's ID in this case. -- `container`: The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that you can use to resolve registered resources. +```js title="integration-tests/setup.js" +const { MetadataStorage } = require("@mikro-orm/core") -In the subscriber function, you use the container to resolve the Logger utility and log a message in the console. Also, assuming you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) that sends an order confirmation email, you execute it in the subscriber. +MetadataStorage.clear() +``` *** -## Test the Subscriber +## Add Test Commands -To test the subscriber, start the Medusa application: +Finally, add the following scripts to `package.json`: -```bash npm2yarn -npm run dev +```json title="package.json" +"scripts": { + // ... + "test:integration:http": "TEST_TYPE=integration:http NODE_OPTIONS=--experimental-vm-modules jest --silent=false --runInBand --forceExit", + "test:integration:modules": "TEST_TYPE=integration:modules NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit", + "test:unit": "TEST_TYPE=unit NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit" +}, ``` -Then, try placing an order either using Medusa's API routes or the [Next.js Storefront](https://docs.medusajs.com/learn/storefront-development/nextjs-starter/index.html.md). You'll see the following message in the terminal: +You now have two commands: -```bash -info: Processing order.placed which has 1 subscribers -Sending confirmation email... -``` +- `test:integration:http` to run integration tests (for example, for API routes and workflows) available under the `integration-tests/http` directory. +- `test:integration:modules` to run integration tests for modules available in any `__tests__` directory under `src/modules`. +- `test:unit` to run unit tests in any `__tests__` directory under the `src` directory. -The first message indicates that the `order.placed` event was emitted, and the second one is the message logged from the subscriber. +Medusa's Testing Framework works for integration tests only. You can write unit tests using Jest. *** -## Event Module +## Test Tools and Writing Tests -The subscription and emitting of events is handled by an Event Module, an architectural module that implements the pub/sub functionalities of Medusa's event system. +The next chapters explain how to use the testing tools provided by `@medusajs/test-utils` to write tests. -Medusa provides two Event Modules out of the box: -- [Local Event Module](https://docs.medusajs.com/resources/architectural-modules/event/local/index.html.md), used by default. It's useful for development, as you don't need additional setup to use it. -- [Redis Event Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md), which is useful in production. It uses [Redis](https://redis.io/) to implement Medusa's pub/sub events system. +# Customize Medusa Admin Dashboard -Medusa's [architecture](https://docs.medusajs.com/learn/introduction/architecture/index.html.md) also allows you to build a custom Event Module that uses a different service or logic to implement the pub/sub system. Learn how to build an Event Module in [this guide](https://docs.medusajs.com/resources/architectural-modules/event/create/index.html.md). +In the previous chapters, you've customized your Medusa application to [add brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), [expose an API route to create brands](https://docs.medusajs.com/learn/customization/custom-features/api-route/index.html.md), and [linked brands to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md). +After customizing and extending your application with new features, you may need to provide an interface for admin users to utilize these features. The Medusa Admin dashboard is extendable, allowing you to: -# Data Models Advanced Guides +- Insert components, called [widgets](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md), on existing pages. +- Add new pages, called [UI Routes](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md). -Data models are created and managed in a module. To learn how to create a data model in a custom module, refer to the [Modules chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). +From these customizations, you can send requests to custom API routes, allowing admin users to manage custom resources on the dashboard -In the next chapters, you'll learn about defining data models in more details. You'll learn about: +*** -- The different property types available. -- How to set a property as a primary key. -- How to create and manage relationships. -- How to configure properties, such as making them nullable or searchable. -- How to manually write migrations. +## Next Chapters: View Brands in Dashboard +In the next chapters, you'll continue with the brands example to: -# Medusa Container +- Add a new section to the product details page that shows the product's brand. +- Add a new page in the dashboard that shows all brands in the store. -In this chapter, you’ll learn about the Medusa container and how to use it. -## What is the Medusa Container? +# Extend Core Commerce Features -The Medusa container is a registry of framework and commerce tools that's accessible across your application. Medusa automatically registers these tools in the container, including custom ones that you've built, so that you can use them in your customizations. +In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features. -In other platforms, if you have a resource A (for example, a class) that depends on a resource B, you have to manually add resource B to the container or specify it beforehand as A's dependency, which is often done in a file separate from A's code. This becomes difficult to manage as you maintain larger applications with many changing dependencies. +In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run. -Medusa simplifies this process by giving you access to the container, with the tools or resources already registered, at all times in your customizations. When you reach a point in your code where you need a tool, you resolve it from the container and use it. +Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs: -For example, consider you're creating an API route that retrieves products based on filters using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md), a tool that fetches data across the application. In the API route's function, you can resolve Query from the container passed to the API route and use it: +- [Module Links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects. +- [Workflow Hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds. +- [Additional Data in API Routes](https://docs.medusajs.com/learn/fundamentals/api-routes/additional-data/index.html.md): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow. -```ts highlights={highlights} -import { MedusaRequest, MedusaResponse } from "@medusajs/framework" -import { ContainerRegistrationKeys } from "@medusajs/framework/utils" +*** -export async function GET( - req: MedusaRequest, - res: MedusaResponse -) { - const query = req.scope.resolve("query") +## Next Chapters: Link Brands to Products Example - const { data: products } = await query.graph({ - entity: "product", - fields: ["*"], - filters: { - id: "prod_123", - }, - }) +The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](https://docs.medusajs.com/learn/customization/custom-features/index.html.md) to: - res.json({ - products, - }) -} -``` +- Link brands from the custom [Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to products from Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md). +- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product. +- Retrieve a product's associated brand's details. -The API route accepts as a first parameter a request object that has a `scope` property, which is the Medusa container. It has a `resolve` method that resolves a resource from the container by the key it's registered with. -You can learn more about how Query works in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). +# Customizations Next Steps: Learn the Fundamentals -*** +The previous guides introduced Medusa's different concepts and how you can use them to customize Medusa for a realistic use case, You added brands to your application, linked them to products, customized the admin dashboard, and integrated a third-party CMS. -## List of Resources Registered in the Medusa Container +The next chapters will cover each of these concepts in depth, with the different ways you can use them, their options or configurations, and more advanced features that weren't covered in the previous guides. While you can start building with Medusa, it's highly recommended to follow the next chapters for a better understanding of Medusa's fundamentals. -Find a full list of the registered resources and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md) +## Helpful Resources Guides + +The [Development Resources](https://docs.medusajs.com/resources/index.html.md) documentation provides more helpful guides and references for your development journey. Some of these guides and references include: + +3. [Commerce Modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md): Browse the list of commerce modules in Medusa and their references to learn how to use them. +4. [Service Factory Reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md): Learn about the methods generated by `MedusaService` with examples. +5. [Workflows Reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md): Browse the list of core workflows and their hooks that are useful for your customizations. +6. [Admin Injection Zones](https://docs.medusajs.com/resources/admin-widget-injection-zones/index.html.md): Browse the injection zones in the Medusa Admin to learn where you can inject widgets. *** -## How to Resolve From the Medusa Container +## More Examples in Recipes -This section gives quick examples of how to resolve resources from the Medusa container in customizations other than an API route, which is covered in the section above. +In the Development Resources documentation, you'll also find step-by-step guides for different use cases, such as building a marketplace, digital products, and more. -### Subscriber +Refer to the [Recipes](https://docs.medusajs.com/resources/recipes/index.html.md) documentation to learn more. -A [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) function, which is executed when an event is emitted, accepts as a parameter an object with a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key: -```ts highlights={subscriberHighlights} -import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" -import { ContainerRegistrationKeys } from "@medusajs/framework/utils" +# Integrate Third-Party Systems -export default async function productCreateHandler({ - event: { data }, - container, -}: SubscriberArgs<{ id: string }>) { - const query = container.resolve(ContainerRegistrationKeys.QUERY) +Commerce applications often connect to third-party systems that provide additional or specialized features. For example, you may integrate a Content-Management System (CMS) for rich content features, a payment provider to process credit-card payments, and a notification service to send emails. - const { data: products } = await query.graph({ - entity: "product", - fields: ["*"], - filters: { - id: data.id, - }, - }) +Medusa's framework facilitates integrating these systems and orchestrating operations across them, saving you the effort of managing them yourself. You won't find those capabilities in other commerce platforms that in these scenarios become a bottleneck to building customizations and iterating quickly. - console.log(`You created a product with the title ${products[0].title}`) -} +In Medusa, you integrate a third-party system by: -export const config: SubscriberConfig = { - event: `product.created`, -} -``` +1. Creating a module whose service provides the methods to connect to and perform operations in the third-party system. +2. Building workflows that complete tasks spanning across systems. You use the module that integrates a third-party system in the workflow's steps. +3. Executing the workflows you built in an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), at a scheduled time, or when an event is emitted. -### Scheduled Job +*** -A [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) function, which is executed at a specified interval, accepts the Medusa container as a parameter. Use its `resolve` method to resolve a resource by its registration key: +## Next Chapters: Sync Brands Example -```ts highlights={scheduledJobHighlights} -import { MedusaContainer } from "@medusajs/framework/types" -import { ContainerRegistrationKeys } from "@medusajs/framework/utils" +In the previous chapters, you've [added brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) to your Medusa application. In the next chapters, you will: -export default async function myCustomJob( - container: MedusaContainer -) { - const query = container.resolve(ContainerRegistrationKeys.QUERY) +1. Integrate a dummy third-party CMS in the Brand Module. +2. Sync brands to the CMS when a brand is created. +3. Sync brands from the CMS at a daily schedule. - const { data: products } = await query.graph({ - entity: "product", - fields: ["*"], - filters: { - id: "prod_123", - }, - }) - console.log( - `You have ${products.length} matching your filters.` - ) -} +# Re-Use Customizations with Plugins -export const config = { - name: "every-minute-message", - // execute every minute - schedule: "* * * * *", -} -``` +In the previous chapters, you've learned important concepts related to creating modules, implementing commerce features in workflows, exposing those features in API routes, customizing the Medusa Admin dashboard with Admin Extensions, and integrating third-party systems. -### Workflow Step +You've implemented the brands example within a single Medusa application. However, this approach is not scalable when you want to reuse your customizations across multiple projects. -A [step in a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function where you build durable execution logic across multiple modules, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key: +To reuse your customizations across multiple Medusa applications, such as implementing brands in different projects, you can create a plugin. A plugin is an NPM package that encapsulates your customizations and can be installed in any Medusa application. Plugins can include modules, workflows, API routes, Admin Extensions, and more. -```ts highlights={workflowStepsHighlight} -import { - createStep, - StepResponse, -} from "@medusajs/framework/workflows-sdk" -import { ContainerRegistrationKeys } from "@medusajs/framework/utils" +![Diagram showcasing how the Brand Plugin would add its resources to any application it's installed in](https://res.cloudinary.com/dza7lstvk/image/upload/v1737540091/Medusa%20Book/brand-plugin_bk9zi9.jpg) -const step1 = createStep("step-1", async (_, { container }) => { - const query = container.resolve(ContainerRegistrationKeys.QUERY) +Medusa provides the tooling to create a plugin package, test it in a local Medusa application, and publish it to NPM. - const { data: products } = await query.graph({ - entity: "product", - fields: ["*"], - filters: { - id: "prod_123", - }, - }) +To learn more about plugins and how to create them, refer to [this chapter](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). - return new StepResponse(products) -}) -``` -### Module Services and Loaders +# Admin Development -A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of functionalities for a single feature or domain, has its own container, so it can't resolve resources from the Medusa container. +In the next chapters, you'll learn more about possible admin customizations. -Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md). +You can customize the admin dashboard by: +- Adding new sections to existing pages using Widgets. +- Adding new pages using UI Routes. -# Module Link +*** -In this chapter, you’ll learn what a module link is. +## Medusa UI Package -## What is a Module Link? +Medusa provides a Medusa UI package to facilitate your admin development through ready-made components and ensure a consistent design between your customizations and the dashboard’s design. -Since modules are isolated, you can't access another module's data models to add a relation to it or extend it. +Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.md) to learn how to install it and use its components. -Instead, you use a module link. A module link forms an association between two data models of different modules, while maintaining module isolation. +*** + +## Admin Components List + +To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components. + + +# API Routes + +In this chapter, you’ll learn what API Routes are and how to create them. + +## What is an API Route? + +An API Route is an endpoint. It exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems. + +The Medusa core application provides a set of admin and store API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities. *** -## How to Define a Module Link? +## How to Create an API Route? -### 1. Create Link File +An API Route is created in a TypeScript or JavaScript file under the `src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`. -Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines the link using `defineLink` from the Modules SDK and exports it. +![Example of API route in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732808645/Medusa%20Book/route-dir-overview_dqgzmk.jpg) -For example: +Each file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…). -```ts title="src/links/hello-product.ts" highlights={highlights} -import HelloModule from "../modules/hello" -import ProductModule from "@medusajs/medusa/product" -import { defineLink } from "@medusajs/framework/utils" +For example, to create a `GET` API Route at `/hello-world`, create the file `src/api/hello-world/route.ts` with the following content: -export default defineLink( - ProductModule.linkable.product, - HelloModule.linkable.myCustom -) +```ts title="src/api/hello-world/route.ts" +import type { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = ( + req: MedusaRequest, + res: MedusaResponse +) => { + res.json({ + message: "[GET] Hello world!", + }) +} ``` -The `defineLink` function accepts as parameters the link configurations of each module's data model. A module has a special `linkable` property that holds these configurations for its data models. +### Test API Route -In this example, you define a module link between the `hello` module's `MyCustom` data model and the Product Module's `Product` data model. +To test the API route above, start the Medusa application: -### 2. Sync Links +```bash npm2yarn +npm run dev +``` -After defining the link, run the `db:sync-links` command: +Then, send a `GET` request to the `/hello-world` API Route: ```bash -npx medusa db:sync-links +curl http://localhost:9000/hello-world ``` -The Medusa application creates a new table for your link to store the IDs of linked records. +*** -Use this command whenever you make changes to your links. For example, run this command if you remove your link definition file. +## When to Use API Routes -You can also use the `db:migrate` command, which both runs the migrations and syncs the links. +You're exposing custom functionality to be used by a storefront, admin dashboard, or any external application. -*** -## How Module Links Work? +# Custom CLI Scripts -When you define a module link, the Medusa application creates a table in the database for that link. +In this chapter, you'll learn how to create and execute custom scripts from Medusa's CLI tool. -Then, when you create links between records of the data models, the IDs of these data models are stored as a new record in the link's table. +## What is a Custom CLI Script? -![Diagram illustration for links](https://res.cloudinary.com/dza7lstvk/image/upload/v1726482168/Medusa%20Book/Custom_Link_Illustration_fsisfa.jpg) +A custom CLI script is a function to execute through Medusa's CLI tool. This is useful when creating custom Medusa tooling to run through the CLI. *** -## When to Use Module Links - -- You want to create a relation between data models from different modules. -- You want to extend the data model of another module. +## How to Create a Custom CLI Script? -You want to create a relationship between data models in the same module. Use data model relationships instead. +To create a custom CLI script, create a TypeScript or JavaScript file under the `src/scripts` directory. The file must default export a function. -*** +For example, create the file `src/scripts/my-script.ts` with the following content: -## Define a List Link +```ts title="src/scripts/my-script.ts" +import { + ExecArgs, + IProductModuleService, +} from "@medusajs/framework/types" +import { Modules } from "@medusajs/framework/utils" -By default, the defined link establishes a one-to-one relation: a record of a data model is linked to one record of the other data model. +export default async function myScript({ container }: ExecArgs) { + const productModuleService: IProductModuleService = container.resolve( + Modules.PRODUCT + ) -To specify that a data model can have multiple of its records linked to the other data model's record, use the `isList` option. + const [, count] = await productModuleService + .listAndCountProducts() -For example: + console.log(`You have ${count} product(s)`) +} +``` -```ts -import HelloModule from "../modules/hello" -import ProductModule from "@medusajs/medusa/product" -import { defineLink } from "@medusajs/framework/utils" +The function receives as a parameter an object having a `container` property, which is an instance of the Medusa Container. Use it to resolve resources in your Medusa application. -export default defineLink( - ProductModule.linkable.product, - { - linkable: HelloModule.linkable.myCustom, - isList: true, - } -) -``` +*** -In this case, you pass an object of configuration as a parameter instead. The object accepts the following properties: +## How to Run Custom CLI Script? -- `linkable`: The data model's link configuration. -- `isList`: Whether multiple records can be linked to one record of the other data model. +To run the custom CLI script, run the Medusa CLI's `exec` command: -In this example, a record of `product` can be linked to more than one record of `myCustom`. +```bash +npx medusa exec ./src/scripts/my-script.ts +``` *** -## Set Delete Cascades on Link +## Custom CLI Script Arguments -To enable delete cascade on a link so that when a record is deleted, its linked records are also deleted, pass the `deleteCascade` property in the object passed to `defineLink`. +Your script can accept arguments from the command line. Arguments are passed to the function's object parameter in the `args` property. For example: ```ts -import HelloModule from "../modules/hello" -import ProductModule from "@medusajs/medusa/product" -import { defineLink } from "@medusajs/framework/utils" +import { ExecArgs } from "@medusajs/framework/types" -export default defineLink( - ProductModule.linkable.product, - { - linkable: HelloModule.linkable.myCustom, - deleteCascade: true, - } -) +export default async function myScript({ args }: ExecArgs) { + console.log(`The arguments you passed: ${args}`) +} ``` -In this example, when a product is deleted, its linked `myCustom` record is also deleted. +Then, pass the arguments in the `exec` command after the file path: +```bash +npx medusa exec ./src/scripts/my-script.ts arg1 arg2 +``` -# Modules -In this chapter, you’ll learn about modules and how to create them. +# Data Models Advanced Guides -## What is a Module? +Data models are created and managed in a module. To learn how to create a data model in a custom module, refer to the [Modules chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). -A module is a reusable package of functionalities related to a single domain or integration. Medusa comes with multiple pre-built modules for core commerce needs, such as the [Cart Module](https://docs.medusajs.com/resources/commerce-modules/cart/index.html.md) that holds the data models and business logic for cart operations. +In the next chapters, you'll learn about defining data models in more details. You'll learn about: -When building a commerce application, you often need to introduce custom behavior specific to your products, tech stack, or your general ways of working. In other commerce platforms, introducing custom business logic and data models requires setting up separate applications to manage these customizations. +- The different property types available. +- How to set a property as a primary key. +- How to create and manage relationships. +- How to configure properties, such as making them nullable or searchable. +- How to manually write migrations. -Medusa removes this overhead by allowing you to easily write custom modules that integrate into the Medusa application without implications on the existing setup. You can also re-use your modules across Medusa projects. -As you learn more about Medusa, you will see that modules are central to customizations and integrations. With modules, your Medusa application can turn into a middleware solution for your commerce ecosystem. +# Environment Variables -- You want to build a custom feature related to a single domain or integrate a third-party service. +In this chapter, you'll learn how environment variables are loaded in Medusa. -- You want to create a reusable package of customizations that include not only modules, but also API routes, workflows, and other customizations. Instead, use a [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). +## System Environment Variables -*** +The Medusa application loads and uses system environment variables. -## How to Create a Module? +For example, if you set the `PORT` environment variable to `8000`, the Medusa application runs on that port instead of `9000`. -In a module, you define data models that represent new tables in the database, and you manage these models in a class called a service. Then, the Medusa application registers the module's service in the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) so that you can build commerce flows and features around the functionalities provided by the module. +In production, you should always use system environment variables that you set through your hosting provider. -In this section, you'll build a Blog Module that has a `Post` data model and a service to manage that data model, you'll expose an API endpoint to create a blog post. +*** -Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/blog`. +## Environment Variables in .env Files -### 1. Create Data Model +During development, it's easier to set environment variables in a `.env` file in your repository. -A data model represents a table in the database. You create data models using Medusa's data modeling language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations. +Based on your `NODE_ENV` system environment variable, Medusa will try to load environment variables from the following `.env` files: -You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a `Post` data model in the Blog Module, create the file `src/modules/blog/models/post.ts` with the following content: +As of [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0), `NODE_ENV` defaults to `production` when using `medusa start`. Otherwise, it defaults to `development`. -![Updated directory overview after adding the data model](https://res.cloudinary.com/dza7lstvk/image/upload/v1732806790/Medusa%20Book/blog-dir-overview-1_jfvovj.jpg) +|\`.env\`| +|---|---| +|\`NODE\_ENV\`|\`.env\`| +|\`NODE\_ENV\`|\`.env.production\`| +|\`NODE\_ENV\`|\`.env.staging\`| +|\`NODE\_ENV\`|\`.env.test\`| -```ts title="src/modules/blog/models/post.ts" -import { model } from "@medusajs/framework/utils" +### Set Environment in `loadEnv` -const Post = model.define("post", { - id: model.id().primaryKey(), - title: model.text(), -}) +In the `medusa-config.ts` file of your Medusa application, you'll find a `loadEnv` function used that accepts `process.env.NODE_ENV` as a first parameter. -export default Post -``` +This function is responsible for loading the correct `.env` file based on the value of `process.env.NODE_ENV`. -You define the data model using the `define` method of the DML. It accepts two parameters: +To ensure that the correct `.env` file is loaded as shown in the table above, only specify `development`, `production`, `staging` or `test` as the value of `process.env.NODE_ENV` or as the parameter of `loadEnv`. -1. The first one is the name of the data model's table in the database. Use snake-case names. -2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods, such as `text` and `id`. - - Data models automatically have the date properties `created_at`, `updated_at`, and `deleted_at`, so you don't need to add them manually. +*** -Learn about other property types in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/property-types/index.html.md). +## Environment Variables for Admin Customizations -The code snippet above defines a `Post` data model with `id` and `title` properties. +Since the Medusa Admin is built on top of [Vite](https://vite.dev/), you prefix the environment variables you want to use in a widget or UI route with `VITE_`. Then, you can access or use them with the `import.meta.env` object. -### 2. Create Service +Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md). -You perform database operations on your data models in a service, which is a class exported by the module and acts like an interface to its functionalities. Medusa registers the service in its [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), allowing you to resolve and use it when building custom commerce flows. +*** -You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, to create the Blog Module's service, create the file `src/modules/blog/service.ts` with the following content: +## Predefined Medusa Environment Variables -![Updated directory overview after adding the service](https://res.cloudinary.com/dza7lstvk/image/upload/v1732807230/Medusa%20Book/blog-dir-overview-2_avzb9l.jpg) - -```ts title="src/modules/blog/service.ts" highlights={highlights} -import { MedusaService } from "@medusajs/framework/utils" -import Post from "./models/post" - -class BlogModuleService extends MedusaService({ - Post, -}){ -} - -export default BlogModuleService -``` - -Your module's service extends a class generated by `MedusaService` from the Modules SDK. This class comes with generated methods for data-management Create, Read, Update, and Delete (CRUD) operations on each of your modules, saving your time that can be spent on building custom business logic. - -The `MedusaService` function accepts an object of data models to generate methods for. You can pass all data models in your module in this object. - -For example, the `BlogModuleService` now has a `createPosts` method to create post records, and a `retrievePost` method to retrieve a post record. The suffix of each method (except for `retrieve`) is the pluralized name of the data model. - -Find all methods generated by the `MedusaService` in [this reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) +The Medusa application uses the following predefined environment variables that you can set: -If a module doesn't have data models, such as when it's integrating a third-party service, it doesn't need to extend `MedusaService`. +You should opt for setting configurations in `medusa-config.ts` where possible. For a full list of Medusa configurations, refer to [this documenation](https://docs.medusajs.com/resources/references/medusa-config/index.html.md). -### 3. Export Module Definition +|Environment Variable|Description|Default| +|---|---|---|---|---| +| +| +| +| +||The URL to connect to the PostgreSQL database. Only used if || +||URLs of storefronts that can access the Medusa backend's Store APIs. Only used if || +||URLs of admin dashboards that can access the Medusa backend's Admin APIs. Only used if || +||URLs of clients that can access the Medusa backend's authentication routes. Only used if || +||A random string used to create authentication tokens in the http layer. Only used if || +||A random string used to create cookie tokens in the http layer. Only used if || +||The URL to the Medusa backend. Only used if || +| +| +| +| +| +| +| +| +||The allowed levels to log. Learn more in || +||The file to save logs in. By default, logs aren't saved in any file. Learn more in || +||Whether to disable analytics data collection. Learn more in || -The final piece to a module is its definition, which is exported in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its main service. Medusa will then register the main service in the container under the module's name. -So, to export the definition of the Blog Module, create the file `src/modules/blog/index.ts` with the following content: +# Events and Subscribers -![Updated directory overview after adding the module definition](https://res.cloudinary.com/dza7lstvk/image/upload/v1732808511/Medusa%20Book/blog-dir-overview-3_dcgjaa.jpg) +In this chapter, you’ll learn about Medusa's event system, and how to handle events with subscribers. -```ts title="src/modules/blog/index.ts" highlights={moduleDefinitionHighlights} -import BlogModuleService from "./service" -import { Module } from "@medusajs/framework/utils" +## Handle Core Commerce Flows with Events -export const BLOG_MODULE = "blog" +When building commerce digital applications, you'll often need to perform an action after a commerce operation is performed. For example, sending an order confirmation email when the customer places an order, or syncing data that's updated in Medusa to a third-party system. -export default Module(BLOG_MODULE, { - service: BlogModuleService, -}) -``` +Medusa emits events when core commerce features are performed, and you can listen to and handle these events in asynchronous functions. You can think of Medusa's events like you'd think about webhooks in other commerce platforms, but instead of having to setup separate applications to handle webhooks, your efforts only go into writing the logic right in your Medusa codebase. -You use `Module` from the Modules SDK to create the module's definition. It accepts two parameters: +You listen to an event in a subscriber, which is an asynchronous function that's executed when its associated event is emitted. -1. The name that the module's main service is registered under (`blog`). -2. An object with a required property `service` indicating the module's main service. +![A diagram showcasing an example of how an event is emitted when an order is placed.](https://res.cloudinary.com/dza7lstvk/image/upload/v1732277948/Medusa%20Book/order-placed-event-example_e4e4kw.jpg) -You export `BLOG_MODULE` to reference the module's name more reliably when resolving its service in other customizations. +Subscribers are useful to perform actions that aren't integral to the original flow. For example, you can handle the `order.placed` event in a subscriber that sends a confirmation email to the customer. The subscriber has no impact on the original order-placement flow, as it's executed outside of it. -### 4. Add Module to Medusa's Configurations +If the action you're performing is integral to the main flow of the core commerce feature, use [workflow hooks](https://docs.medusajs.com/learn/fundamentals/workflows/workflow-hooks/index.html.md) instead. -If you're creating the module in a plugin, this step isn't required as the module is registered when the plugin is registered. Learn more about plugins in [this documentation](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). +### List of Emitted Events -Once you finish building the module, add it to Medusa's configurations to start using it. Medusa will then register the module's main service in the Medusa container, allowing you to resolve and use it in other customizations. +Find a list of all emitted events in [this reference](https://docs.medusajs.com/resources/events-reference/index.html.md). -In `medusa-config.ts`, add a `modules` property and pass an array with your custom module: +*** -```ts title="medusa-config.ts" highlights={[["7"]]} -module.exports = defineConfig({ - projectConfig: { - // ... - }, - modules: [ - { - resolve: "./src/modules/blog", - }, - ], -}) -``` +## How to Create a Subscriber? -Each object in the `modules` array has a `resolve` property, whose value is either a path to the module's directory, or an `npm` package’s name. +You create a subscriber in a TypeScript or JavaScript file under the `src/subscribers` directory. The file exports the function to execute and the subscriber's configuration that indicate what event(s) it listens to. -### 5. Generate Migrations +For example, create the file `src/subscribers/order-placed.ts` with the following content: -Since data models represent tables in the database, you define how they're created in the database with migrations. A migration is a TypeScript or JavaScript file that defines database changes made by a module. +![Example of subscriber file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732866244/Medusa%20Book/subscriber-dir-overview_pusyeu.jpg) -Migrations are useful when you re-use a module or you're working in a team, so that when one member of a team makes a database change, everyone else can reflect it on their side by running the migrations. +```ts title="src/subscribers/product-created.ts" +import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" +import { sendOrderConfirmationWorkflow } from "../workflows/send-order-confirmation" -You don't have to write migrations yourself. Medusa's CLI tool has a command that generates the migrations for you. You can also use this command again when you make changes to the module at a later point, and it will generate new migrations for that change. +export default async function orderPlacedHandler({ + event: { data }, + container, +}: SubscriberArgs<{ id: string }>) { + const logger = container.resolve("logger") -To generate a migration for the Blog Module, run the following command in your Medusa application's directory: + logger.info("Sending confirmation email...") -If you're creating the module in a plugin, use the [plugin:db:generate command](https://docs.medusajs.com/resources/medusa-cli/commands/plugin#plugindbgenerate/index.html.md) instead. + await sendOrderConfirmationWorkflow(container) + .run({ + input: { + id: data.id, + }, + }) +} -```bash -npx medusa db:generate blog +export const config: SubscriberConfig = { + event: `order.placed`, +} ``` -The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following: +This subscriber file exports: -```ts -import { Migration } from "@mikro-orm/migrations" +- An asynchronous subscriber function that's executed whenever the associated event, which is `order.placed` is triggered. +- A configuration object with an `event` property whose value is the event the subscriber is listening to. You can also pass an array of event names to listen to multiple events in the same subscriber. -export class Migration20241121103722 extends Migration { +The subscriber function receives an object as a parameter that has the following properties: - async up(): Promise { - this.addSql("create table if not exists \"post\" (\"id\" text not null, \"title\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"post_pkey\" primary key (\"id\"));") - } +- `event`: An object with the event's details. The `data` property contains the data payload of the event emitted, which is the order's ID in this case. +- `container`: The [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) that you can use to resolve registered resources. - async down(): Promise { - this.addSql("drop table if exists \"post\" cascade;") - } +In the subscriber function, you use the container to resolve the Logger utility and log a message in the console. Also, assuming you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) that sends an order confirmation email, you execute it in the subscriber. -} -``` +*** -In the migration class, the `up` method creates the table `post` and defines its columns using PostgreSQL syntax. The `down` method drops the table. +## Test the Subscriber -### 6. Run Migrations +To test the subscriber, start the Medusa application: -To reflect the changes in the generated migration file on the database, run the `db:migrate` command: +```bash npm2yarn +npm run dev +``` -If you're creating the module in a plugin, run this command on the Medusa application that the plugin is installed in. +Then, try placing an order either using Medusa's API routes or the [Next.js Storefront](https://docs.medusajs.com/learn/storefront-development/nextjs-starter/index.html.md). You'll see the following message in the terminal: ```bash -npx medusa db:migrate +info: Processing order.placed which has 1 subscribers +Sending confirmation email... ``` -This creates the `post` table in the database. +The first message indicates that the `order.placed` event was emitted, and the second one is the message logged from the subscriber. *** -## Test the Module - -Since the module's main service is registered in the Medusa container, you can resolve it in other customizations to use its methods. - -To test out the Blog Module, you'll add the functionality to create a post in a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function that performs a task in a series of steps with rollback logic. Then, you'll expose an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) that creates a blog post by executing the workflow. - -By building a commerce feature in a workflow, you can execute it in other customizations while ensuring data consistency across systems. If an error occurs during execution, every step has its own rollback logic to undo its actions. Workflows have other special features which you can learn about in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). +## Event Module -To create the workflow, create the file `src/workflows/create-post.ts` with the following content: +The subscription and emitting of events is handled by an Event Module, an architectural module that implements the pub/sub functionalities of Medusa's event system. -```ts title="src/workflows/create-post.ts" highlights={workflowHighlights} -import { - createStep, - createWorkflow, - StepResponse, - WorkflowResponse, -} from "@medusajs/framework/workflows-sdk" -import { BLOG_MODULE } from "../modules/blog" -import BlogModuleService from "../modules/blog/service" +Medusa provides two Event Modules out of the box: -type CreatePostWorkflowInput = { - title: string -} +- [Local Event Module](https://docs.medusajs.com/resources/architectural-modules/event/local/index.html.md), used by default. It's useful for development, as you don't need additional setup to use it. +- [Redis Event Module](https://docs.medusajs.com/resources/architectural-modules/event/redis/index.html.md), which is useful in production. It uses [Redis](https://redis.io/) to implement Medusa's pub/sub events system. -const createPostStep = createStep( - "create-post", - async ({ title }: CreatePostWorkflowInput, { container }) => { - const blogModuleService: BlogModuleService = container.resolve(BLOG_MODULE) +Medusa's [architecture](https://docs.medusajs.com/learn/introduction/architecture/index.html.md) also allows you to build a custom Event Module that uses a different service or logic to implement the pub/sub system. Learn how to build an Event Module in [this guide](https://docs.medusajs.com/resources/architectural-modules/event/create/index.html.md). - const post = await blogModuleService.createPosts({ - title, - }) - return new StepResponse(post, post) - }, - async (post, { container }) => { - const blogModuleService: BlogModuleService = container.resolve(BLOG_MODULE) +# Medusa Container - await blogModuleService.deletePosts(post.id) - } -) +In this chapter, you’ll learn about the Medusa container and how to use it. -export const createPostWorkflow = createWorkflow( - "create-post", - (postInput: CreatePostWorkflowInput) => { - const post = createPostStep(postInput) +## What is the Medusa Container? - return new WorkflowResponse(post) - } -) -``` +The Medusa container is a registry of framework and commerce tools that's accessible across your application. Medusa automatically registers these tools in the container, including custom ones that you've built, so that you can use them in your customizations. -The workflow has a single step `createPostStep` that creates a post. In the step, you resolve the Blog Module's service from the Medusa container, which the step receives as a parameter. Then, you create the post using the method `createPosts` of the service, which was generated by `MedusaService`. +In other platforms, if you have a resource A (for example, a class) that depends on a resource B, you have to manually add resource B to the container or specify it beforehand as A's dependency, which is often done in a file separate from A's code. This becomes difficult to manage as you maintain larger applications with many changing dependencies. -The step also has a compensation function, which is a function passed as a third-parameter to `createStep` that implements the logic to rollback the change made by a step in case an error occurs during the workflow's execution. +Medusa simplifies this process by giving you access to the container, with the tools or resources already registered, at all times in your customizations. When you reach a point in your code where you need a tool, you resolve it from the container and use it. -You'll now execute that workflow in an API route to expose the feature of creating blog posts to clients. To create an API route, create the file `src/api/blog/posts/route.ts` with the following content: +For example, consider you're creating an API route that retrieves products based on filters using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md), a tool that fetches data across the application. In the API route's function, you can resolve Query from the container passed to the API route and use it: -```ts -import type { - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import { - createPostWorkflow, -} from "../../../workflows/create-post" +```ts highlights={highlights} +import { MedusaRequest, MedusaResponse } from "@medusajs/framework" +import { ContainerRegistrationKeys } from "@medusajs/framework/utils" -export async function POST( - req: MedusaRequest, +export async function GET( + req: MedusaRequest, res: MedusaResponse ) { - const { result: post } = await createPostWorkflow(req.scope) - .run({ - input: { - title: "My Post", - }, - }) + const query = req.scope.resolve("query") + + const { data: products } = await query.graph({ + entity: "product", + fields: ["*"], + filters: { + id: "prod_123", + }, + }) res.json({ - post, + products, }) } ``` -This adds a `POST` API route at `/blog/posts`. In the API route, you execute the `createPostWorkflow` by invoking it, passing it the Medusa container in `req.scope`, then invoking the `run` method. In the `run` method, you pass the workflow's input in the `input` property. +The API route accepts as a first parameter a request object that has a `scope` property, which is the Medusa container. It has a `resolve` method that resolves a resource from the container by the key it's registered with. -To test this out, start the Medusa application: +You can learn more about how Query works in [this chapter](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). -```bash npm2yarn -npm run dev -``` +*** -Then, send a `POST` request to `/blog/posts`: - -```bash -curl -X POST http://localhost:9000/blog/posts -``` - -This will create a post and return it in the response: - -```json -{ - "post": { - "id": "123...", - "title": "My Post", - "created_at": "...", - "updated_at": "..." - } -} -``` - -You can also execute the workflow from a [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) when an event occurs, or from a [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) to run it at a specified interval. +## List of Resources Registered in the Medusa Container +Find a full list of the registered resources and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md) -# Scheduled Jobs +*** -In this chapter, you’ll learn about scheduled jobs and how to use them. +## How to Resolve From the Medusa Container -## What is a Scheduled Job? +This section gives quick examples of how to resolve resources from the Medusa container in customizations other than an API route, which is covered in the section above. -When building your commerce application, you may need to automate tasks and run them repeatedly at a specific schedule. For example, you need to automatically sync products to a third-party service once a day. +### Subscriber -In other commerce platforms, this feature isn't natively supported. Instead, you have to setup a separate application to execute cron jobs, which adds complexity as to how you expose this task to be executed in a cron job, or how do you debug it when it's not running within the platform's tooling. +A [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) function, which is executed when an event is emitted, accepts as a parameter an object with a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key: -Medusa removes this overhead by supporting this feature natively with scheduled jobs. A scheduled job is an asynchronous function that the Medusa application runs at the interval you specify during the Medusa application's runtime. Your efforts are only spent on implementing the functionality performed by the job, such as syncing products to an ERP. +```ts highlights={subscriberHighlights} +import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework" +import { ContainerRegistrationKeys } from "@medusajs/framework/utils" -- You want the action to execute at a specified schedule while the Medusa application **isn't** running. Instead, use the operating system's equivalent of a cron job. -- You want to execute the action once when the application loads. Use [loaders](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md) instead. -- You want to execute the action if an event occurs. Use [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) instead. +export default async function productCreateHandler({ + event: { data }, + container, +}: SubscriberArgs<{ id: string }>) { + const query = container.resolve(ContainerRegistrationKeys.QUERY) -*** + const { data: products } = await query.graph({ + entity: "product", + fields: ["*"], + filters: { + id: data.id, + }, + }) -## How to Create a Scheduled Job? + console.log(`You created a product with the title ${products[0].title}`) +} -You create a scheduled job in a TypeScript or JavaScript file under the `src/jobs` directory. The file exports the asynchronous function to run, and the configurations indicating the schedule to run the function. +export const config: SubscriberConfig = { + event: `product.created`, +} +``` -For example, create the file `src/jobs/hello-world.ts` with the following content: +### Scheduled Job -![Example of scheduled job file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732866423/Medusa%20Book/scheduled-job-dir-overview_ediqgm.jpg) +A [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) function, which is executed at a specified interval, accepts the Medusa container as a parameter. Use its `resolve` method to resolve a resource by its registration key: -```ts title="src/jobs/hello-world.ts" highlights={highlights} +```ts highlights={scheduledJobHighlights} import { MedusaContainer } from "@medusajs/framework/types" +import { ContainerRegistrationKeys } from "@medusajs/framework/utils" -export default async function greetingJob(container: MedusaContainer) { - const logger = container.resolve("logger") +export default async function myCustomJob( + container: MedusaContainer +) { + const query = container.resolve(ContainerRegistrationKeys.QUERY) - logger.info("Greeting!") + const { data: products } = await query.graph({ + entity: "product", + fields: ["*"], + filters: { + id: "prod_123", + }, + }) + + console.log( + `You have ${products.length} matching your filters.` + ) } export const config = { - name: "greeting-every-minute", + name: "every-minute-message", + // execute every minute schedule: "* * * * *", } ``` -You export an asynchronous function that receives the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. In the function, you resolve the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) from the Medusa container and log a message. +### Workflow Step -You also export a `config` object that has the following properties: +A [step in a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function where you build durable execution logic across multiple modules, accepts in its second parameter a `container` property, whose value is the Medusa container. Use its `resolve` method to resolve a resource by its registration key: -- `name`: A unique name for the job. -- `schedule`: A string that holds a [cron expression](https://crontab.guru/) indicating the schedule to run the job. +```ts highlights={workflowStepsHighlight} +import { + createStep, + StepResponse, +} from "@medusajs/framework/workflows-sdk" +import { ContainerRegistrationKeys } from "@medusajs/framework/utils" -This scheduled job executes every minute and logs into the terminal `Greeting!`. +const step1 = createStep("step-1", async (_, { container }) => { + const query = container.resolve(ContainerRegistrationKeys.QUERY) -### Test the Scheduled Job + const { data: products } = await query.graph({ + entity: "product", + fields: ["*"], + filters: { + id: "prod_123", + }, + }) -To test out your scheduled job, start the Medusa application: + return new StepResponse(products) +}) +``` -```bash npm2yarn -npm run dev +### Module Services and Loaders + +A [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of functionalities for a single feature or domain, has its own container, so it can't resolve resources from the Medusa container. + +Learn more about the module's container in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md). + + +# Module Link + +In this chapter, you’ll learn what a module link is. + +## What is a Module Link? + +Since modules are isolated, you can't access another module's data models to add a relation to it or extend it. + +Instead, you use a module link. A module link forms an association between two data models of different modules, while maintaining module isolation. + +*** + +## How to Define a Module Link? + +### 1. Create Link File + +Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines the link using `defineLink` from the Modules SDK and exports it. + +For example: + +```ts title="src/links/hello-product.ts" highlights={highlights} +import HelloModule from "../modules/hello" +import ProductModule from "@medusajs/medusa/product" +import { defineLink } from "@medusajs/framework/utils" + +export default defineLink( + ProductModule.linkable.product, + HelloModule.linkable.myCustom +) ``` -After a minute, the following message will be logged to the terminal: +The `defineLink` function accepts as parameters the link configurations of each module's data model. A module has a special `linkable` property that holds these configurations for its data models. + +In this example, you define a module link between the `hello` module's `MyCustom` data model and the Product Module's `Product` data model. + +### 2. Sync Links + +After defining the link, run the `db:sync-links` command: ```bash -info: Greeting! +npx medusa db:sync-links ``` +The Medusa application creates a new table for your link to store the IDs of linked records. + +Use this command whenever you make changes to your links. For example, run this command if you remove your link definition file. + +You can also use the `db:migrate` command, which both runs the migrations and syncs the links. + *** -## Example: Sync Products Once a Day +## How Module Links Work? -In this section, you'll find a brief example of how you use a scheduled job to sync products to a third-party service. +When you define a module link, the Medusa application creates a table in the database for that link. -When implementing flows spanning across systems or [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), you use [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). A workflow is a task made up of a series of steps, and you construct it like you would a regular function, but it's a special function that supports rollback mechanism in case of errors, background execution, and more. +Then, when you create links between records of the data models, the IDs of these data models are stored as a new record in the link's table. -You can learn how to create a workflow in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), but this example assumes you already have a `syncProductToErpWorkflow` implemented. To execute this workflow once a day, create a scheduled job at `src/jobs/sync-products.ts` with the following content: +![Diagram illustration for links](https://res.cloudinary.com/dza7lstvk/image/upload/v1726482168/Medusa%20Book/Custom_Link_Illustration_fsisfa.jpg) -```ts title="src/jobs/sync-products.ts" -import { MedusaContainer } from "@medusajs/framework/types" -import { syncProductToErpWorkflow } from "../workflows/sync-products-to-erp" +*** -export default async function syncProductsJob(container: MedusaContainer) { - await syncProductToErpWorkflow(container) - .run() -} +## When to Use Module Links -export const config = { - name: "sync-products-job", - schedule: "0 0 * * *", -} +- You want to create a relation between data models from different modules. +- You want to extend the data model of another module. + +You want to create a relationship between data models in the same module. Use data model relationships instead. + +*** + +## Define a List Link + +By default, the defined link establishes a one-to-one relation: a record of a data model is linked to one record of the other data model. + +To specify that a data model can have multiple of its records linked to the other data model's record, use the `isList` option. + +For example: + +```ts +import HelloModule from "../modules/hello" +import ProductModule from "@medusajs/medusa/product" +import { defineLink } from "@medusajs/framework/utils" + +export default defineLink( + ProductModule.linkable.product, + { + linkable: HelloModule.linkable.myCustom, + isList: true, + } +) ``` -In the scheduled job function, you execute the `syncProductToErpWorkflow` by invoking it and passing it the container, then invoking the `run` method. You also specify in the exported configurations the schedule `0 0 * * *` which indicates midnight time of every day. +In this case, you pass an object of configuration as a parameter instead. The object accepts the following properties: -The next time you start the Medusa application, it will run this job every day at midnight. +- `linkable`: The data model's link configuration. +- `isList`: Whether multiple records can be linked to one record of the other data model. + +In this example, a record of `product` can be linked to more than one record of `myCustom`. + +*** + +## Set Delete Cascades on Link + +To enable delete cascade on a link so that when a record is deleted, its linked records are also deleted, pass the `deleteCascade` property in the object passed to `defineLink`. + +For example: + +```ts +import HelloModule from "../modules/hello" +import ProductModule from "@medusajs/medusa/product" +import { defineLink } from "@medusajs/framework/utils" + +export default defineLink( + ProductModule.linkable.product, + { + linkable: HelloModule.linkable.myCustom, + deleteCascade: true, + } +) +``` + +In this example, when a product is deleted, its linked `myCustom` record is also deleted. # Plugins @@ -1991,728 +1943,776 @@ The next chapter explains how you can create and publish a plugin. For more resources and guides related to plugins, refer to the [Resources documentation](https://docs.medusajs.com/resources/plugins/index.html.md). -# Workflows +# Modules -In this chapter, you’ll learn about workflows and how to define and execute them. +In this chapter, you’ll learn about modules and how to create them. -## What is a Workflow? +## What is a Module? -In digital commerce you typically have many systems involved in your operations. For example, you may have an ERP system that holds product master data and accounting reports, a CMS system for content, a CRM system for managing customer campaigns, a payment service to process credit cards, and so on. Sometimes you may even have custom built applications that need to participate in the commerce stack. One of the biggest challenges when operating a stack like this is ensuring consistency in the data spread across systems. +A module is a reusable package of functionalities related to a single domain or integration. Medusa comes with multiple pre-built modules for core commerce needs, such as the [Cart Module](https://docs.medusajs.com/resources/commerce-modules/cart/index.html.md) that holds the data models and business logic for cart operations. -Medusa has a built-in durable execution engine to help complete tasks that span multiple systems. You orchestrate your operations across systems in Medusa instead of having to manage it yourself. Other commerce platforms don't have this capability, which makes them a bottleneck to building customizations and iterating quickly. +When building a commerce application, you often need to introduce custom behavior specific to your products, tech stack, or your general ways of working. In other commerce platforms, introducing custom business logic and data models requires setting up separate applications to manage these customizations. -A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow similar to how you create a JavaScript function. +Medusa removes this overhead by allowing you to easily write custom modules that integrate into the Medusa application without implications on the existing setup. You can also re-use your modules across Medusa projects. -However, unlike regular functions, workflows: +As you learn more about Medusa, you will see that modules are central to customizations and integrations. With modules, your Medusa application can turn into a middleware solution for your commerce ecosystem. -- Create an internal representation of your steps, allowing you to track them and their progress. -- Support defining roll-back logic for each step, so that you can handle errors gracefully and your data remain consistent across systems. -- Perform long actions asynchronously, giving you control over when a step starts and finishes. +- You want to build a custom feature related to a single domain or integrate a third-party service. -You implement all custom flows within workflows, then execute them from [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), and [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md). +- You want to create a reusable package of customizations that include not only modules, but also API routes, workflows, and other customizations. Instead, use a [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). *** -## How to Create and Execute a Workflow? - -### 1. Create the Steps +## How to Create a Module? -A workflow is made of a series of steps. A step is created using `createStep` from the Workflows SDK. +In a module, you define data models that represent new tables in the database, and you manage these models in a class called a service. Then, the Medusa application registers the module's service in the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) so that you can build commerce flows and features around the functionalities provided by the module. -Create the file `src/workflows/hello-world.ts` with the following content: +In this section, you'll build a Blog Module that has a `Post` data model and a service to manage that data model, you'll expose an API endpoint to create a blog post. -![Example of workflow file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732866980/Medusa%20Book/workflow-dir-overview_xklukj.jpg) +Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/blog`. -```ts title="src/workflows/hello-world.ts" highlights={step1Highlights} -import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" +### 1. Create Data Model -const step1 = createStep( - "step-1", - async () => { - return new StepResponse(`Hello from step one!`) - } -) -``` +A data model represents a table in the database. You create data models using Medusa's data modeling language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations. -The `createStep` function accepts the step's unique name as a first parameter, and the step's function as a second parameter. +You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a `Post` data model in the Blog Module, create the file `src/modules/blog/models/post.ts` with the following content: -Steps must return an instance of `StepResponse`, whose parameter is the data to return to the workflow executing the step. +![Updated directory overview after adding the data model](https://res.cloudinary.com/dza7lstvk/image/upload/v1732806790/Medusa%20Book/blog-dir-overview-1_jfvovj.jpg) -Steps can accept input parameters. For example, add the following to `src/workflows/hello-world.ts`: +```ts title="src/modules/blog/models/post.ts" +import { model } from "@medusajs/framework/utils" -```ts title="src/workflows/hello-world.ts" highlights={step2Highlights} -type WorkflowInput = { - name: string -} +const Post = model.define("post", { + id: model.id().primaryKey(), + title: model.text(), +}) -const step2 = createStep( - "step-2", - async ({ name }: WorkflowInput) => { - return new StepResponse(`Hello ${name} from step two!`) - } -) +export default Post ``` -This adds another step whose function accepts as a parameter an object with a `name` property. +You define the data model using the `define` method of the DML. It accepts two parameters: -### 2. Create a Workflow +1. The first one is the name of the data model's table in the database. Use snake-case names. +2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods, such as `text` and `id`. + - Data models automatically have the date properties `created_at`, `updated_at`, and `deleted_at`, so you don't need to add them manually. -Next, add the following to the same file to create the workflow using the `createWorkflow` function: +Learn about other property types in [this chapter](https://docs.medusajs.com/learn/fundamentals/data-models/property-types/index.html.md). -```ts title="src/workflows/hello-world.ts" highlights={workflowHighlights} -import { - // other imports... - createWorkflow, - WorkflowResponse, -} from "@medusajs/framework/workflows-sdk" +The code snippet above defines a `Post` data model with `id` and `title` properties. -// ... +### 2. Create Service -const myWorkflow = createWorkflow( - "hello-world", - function (input: WorkflowInput) { - const str1 = step1() - // to pass input - const str2 = step2(input) +You perform database operations on your data models in a service, which is a class exported by the module and acts like an interface to its functionalities. Medusa registers the service in its [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md), allowing you to resolve and use it when building custom commerce flows. - return new WorkflowResponse({ - message: str2, - }) - } -) +You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, to create the Blog Module's service, create the file `src/modules/blog/service.ts` with the following content: -export default myWorkflow -``` +![Updated directory overview after adding the service](https://res.cloudinary.com/dza7lstvk/image/upload/v1732807230/Medusa%20Book/blog-dir-overview-2_avzb9l.jpg) -The `createWorkflow` function accepts the workflow's unique name as a first parameter, and the workflow's function as a second parameter. The workflow can accept input which is passed as a parameter to the function. +```ts title="src/modules/blog/service.ts" highlights={highlights} +import { MedusaService } from "@medusajs/framework/utils" +import Post from "./models/post" -The workflow must return an instance of `WorkflowResponse`, whose parameter is returned to workflow executors. +class BlogModuleService extends MedusaService({ + Post, +}){ +} -### 3. Execute the Workflow +export default BlogModuleService +``` -You can execute a workflow from different customizations: +Your module's service extends a class generated by `MedusaService` from the Modules SDK. This class comes with generated methods for data-management Create, Read, Update, and Delete (CRUD) operations on each of your modules, saving your time that can be spent on building custom business logic. -- Execute in an API route to expose the workflow's functionalities to clients. -- Execute in a subscriber to use the workflow's functionalities when a commerce operation is performed. -- Execute in a scheduled job to run the workflow's functionalities automatically at a specified repeated interval. +The `MedusaService` function accepts an object of data models to generate methods for. You can pass all data models in your module in this object. -To execute the workflow, invoke it passing the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. Then, use its `run` method: +For example, the `BlogModuleService` now has a `createPosts` method to create post records, and a `retrievePost` method to retrieve a post record. The suffix of each method (except for `retrieve`) is the pluralized name of the data model. -### API Route +Find all methods generated by the `MedusaService` in [this reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) -```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" -import type { - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import myWorkflow from "../../workflows/hello-world" +If a module doesn't have data models, such as when it's integrating a third-party service, it doesn't need to extend `MedusaService`. -export async function GET( - req: MedusaRequest, - res: MedusaResponse -) { - const { result } = await myWorkflow(req.scope) - .run({ - input: { - name: "John", - }, - }) +### 3. Export Module Definition - res.send(result) -} -``` +The final piece to a module is its definition, which is exported in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its main service. Medusa will then register the main service in the container under the module's name. -### Subscriber +So, to export the definition of the Blog Module, create the file `src/modules/blog/index.ts` with the following content: -```ts title="src/subscribers/order-placed.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" -import { - type SubscriberConfig, - type SubscriberArgs, -} from "@medusajs/framework" -import myWorkflow from "../workflows/hello-world" +![Updated directory overview after adding the module definition](https://res.cloudinary.com/dza7lstvk/image/upload/v1732808511/Medusa%20Book/blog-dir-overview-3_dcgjaa.jpg) -export default async function handleOrderPlaced({ - event: { data }, - container, -}: SubscriberArgs<{ id: string }>) { - const { result } = await myWorkflow(container) - .run({ - input: { - name: "John", - }, - }) +```ts title="src/modules/blog/index.ts" highlights={moduleDefinitionHighlights} +import BlogModuleService from "./service" +import { Module } from "@medusajs/framework/utils" - console.log(result) -} +export const BLOG_MODULE = "blog" -export const config: SubscriberConfig = { - event: "order.placed", -} +export default Module(BLOG_MODULE, { + service: BlogModuleService, +}) ``` -### Scheduled Job +You use `Module` from the Modules SDK to create the module's definition. It accepts two parameters: -```ts title="src/jobs/message-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"]]} -import { MedusaContainer } from "@medusajs/framework/types" -import myWorkflow from "../workflows/hello-world" +1. The name that the module's main service is registered under (`blog`). +2. An object with a required property `service` indicating the module's main service. -export default async function myCustomJob( - container: MedusaContainer -) { - const { result } = await myWorkflow(container) - .run({ - input: { - name: "John", - }, - }) +You export `BLOG_MODULE` to reference the module's name more reliably when resolving its service in other customizations. - console.log(result.message) -} +### 4. Add Module to Medusa's Configurations -export const config = { - name: "run-once-a-day", - schedule: `0 0 * * *`, -}; -``` +If you're creating the module in a plugin, this step isn't required as the module is registered when the plugin is registered. Learn more about plugins in [this documentation](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). -### 4. Test Workflow +Once you finish building the module, add it to Medusa's configurations to start using it. Medusa will then register the module's main service in the Medusa container, allowing you to resolve and use it in other customizations. -To test out your workflow, start your Medusa application: +In `medusa-config.ts`, add a `modules` property and pass an array with your custom module: -```bash npm2yarn -npm run dev +```ts title="medusa-config.ts" highlights={[["7"]]} +module.exports = defineConfig({ + projectConfig: { + // ... + }, + modules: [ + { + resolve: "./src/modules/blog", + }, + ], +}) ``` -Then, if you added the API route above, send a `GET` request to `/workflow`: +Each object in the `modules` array has a `resolve` property, whose value is either a path to the module's directory, or an `npm` package’s name. -```bash -curl http://localhost:9000/workflow -``` +### 5. Generate Migrations -You’ll receive the following response: +Since data models represent tables in the database, you define how they're created in the database with migrations. A migration is a TypeScript or JavaScript file that defines database changes made by a module. -```json title="Example Response" -{ - "message": "Hello John from step two!" -} -``` +Migrations are useful when you re-use a module or you're working in a team, so that when one member of a team makes a database change, everyone else can reflect it on their side by running the migrations. -*** +You don't have to write migrations yourself. Medusa's CLI tool has a command that generates the migrations for you. You can also use this command again when you make changes to the module at a later point, and it will generate new migrations for that change. -## Access Medusa Container in Workflow Steps +To generate a migration for the Blog Module, run the following command in your Medusa application's directory: -A step receives an object as a second parameter with configurations and context-related properties. One of these properties is the `container` property, which is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to allow you to resolve framework and commerce tools in your application. +If you're creating the module in a plugin, use the [plugin:db:generate command](https://docs.medusajs.com/resources/medusa-cli/commands/plugin#plugindbgenerate/index.html.md) instead. -For example, consider you want to implement a workflow that returns the total products in your application. Create the file `src/workflows/product-count.ts` with the following content: +```bash +npx medusa db:generate blog +``` -```ts title="src/workflows/product-count.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports" -import { - createStep, - StepResponse, - createWorkflow, - WorkflowResponse, -} from "@medusajs/framework/workflows-sdk" +The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following: -const getProductCountStep = createStep( - "get-product-count", - async (_, { container }) => { - const productModuleService = container.resolve("product") +```ts +import { Migration } from "@mikro-orm/migrations" - const [, count] = await productModuleService.listAndCountProducts() +export class Migration20241121103722 extends Migration { - return new StepResponse(count) + async up(): Promise { + this.addSql("create table if not exists \"post\" (\"id\" text not null, \"title\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"post_pkey\" primary key (\"id\"));") } -) - -const productCountWorkflow = createWorkflow( - "product-count", - function () { - const count = getProductCountStep() - return new WorkflowResponse({ - count, - }) + async down(): Promise { + this.addSql("drop table if exists \"post\" cascade;") } -) -export default productCountWorkflow +} ``` -In `getProductCountStep`, you use the `container` to resolve the Product Module's main service. Then, you use its `listAndCountProducts` method to retrieve the total count of products and return it in the step's response. You then execute this step in the `productCountWorkflow`. - -You can now execute this workflow in a custom API route, scheduled job, or subscriber to get the total count of products. - -Find a full list of the registered resources in the Medusa container and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md). You can use these resources in your custom workflows. - - -# Medusa's Architecture - -In this chapter, you'll learn about the architectural layers in Medusa. - -## HTTP, Workflow, and Module Layers - -Medusa is a headless commerce platform. So, storefronts, admin dashboards, and other clients consume Medusa's functionalities through its API routes. - -In a common Medusa application, requests go through four layers in the stack. In order of entry, those are: - -1. API Routes (HTTP): Our API Routes are the typical entry point. -2. Workflows: API Routes consume workflows that hold the opinionated business logic of your application. -3. Modules: Workflows use domain-specific modules for resource management. -4. Data store: Modules query the underlying datastore, which is a PostgreSQL database in common cases. - -These layers of stack can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). - -![Diagram illustrating the HTTP layer](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175296/Medusa%20Book/http-layer_sroafr.jpg) +In the migration class, the `up` method creates the table `post` and defines its columns using PostgreSQL syntax. The `down` method drops the table. -*** +### 6. Run Migrations -## Database Layer +To reflect the changes in the generated migration file on the database, run the `db:migrate` command: -The Medusa application injects into each module a connection to the configured PostgreSQL database. Modules use that connection to read and write data to the database. +If you're creating the module in a plugin, run this command on the Medusa application that the plugin is installed in. -Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). +```bash +npx medusa db:migrate +``` -![Diagram illustrating the database layer](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175379/Medusa%20Book/db-layer_pi7tix.jpg) +This creates the `post` table in the database. *** -## Service Integrations - -Third-party services are integrated through commerce and architectural modules. You also create custom third-party integrations through a custom module. - -Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). - -### Commerce Modules - -[Commerce modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md) integrate third-party services relevant for commerce or user-facing features. For example, you integrate Stripe through a payment module provider. - -![Diagram illustrating the commerce modules integration to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175357/Medusa%20Book/service-commerce_qcbdsl.jpg) +## Test the Module -### Architectural Modules +Since the module's main service is registered in the Medusa container, you can resolve it in other customizations to use its methods. -[Architectural modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) integrate third-party services and systems for architectural features. For example, you integrate Redis as a pub/sub service to send events, or SendGrid to send notifications. +To test out the Blog Module, you'll add the functionality to create a post in a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), which is a special function that performs a task in a series of steps with rollback logic. Then, you'll expose an [API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) that creates a blog post by executing the workflow. -![Diagram illustrating the architectural modules integration to third-party services and systems](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175342/Medusa%20Book/service-arch_ozvryw.jpg) +By building a commerce feature in a workflow, you can execute it in other customizations while ensuring data consistency across systems. If an error occurs during execution, every step has its own rollback logic to undo its actions. Workflows have other special features which you can learn about in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). -*** +To create the workflow, create the file `src/workflows/create-post.ts` with the following content: -## Full Diagram of Medusa's Architecture +```ts title="src/workflows/create-post.ts" highlights={workflowHighlights} +import { + createStep, + createWorkflow, + StepResponse, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" +import { BLOG_MODULE } from "../modules/blog" +import BlogModuleService from "../modules/blog/service" -The following diagram illustrates Medusa's architecture over the three layers. +type CreatePostWorkflowInput = { + title: string +} -![Full diagram illustrating Medusa's architecture](https://res.cloudinary.com/dza7lstvk/image/upload/v1727174897/Medusa%20Book/architectural-diagram-full.jpg) +const createPostStep = createStep( + "create-post", + async ({ title }: CreatePostWorkflowInput, { container }) => { + const blogModuleService: BlogModuleService = container.resolve(BLOG_MODULE) + const post = await blogModuleService.createPosts({ + title, + }) -# Next.js Starter Storefront + return new StepResponse(post, post) + }, + async (post, { container }) => { + const blogModuleService: BlogModuleService = container.resolve(BLOG_MODULE) -The Medusa application is made up of a Node.js server and an admin dashboard. The storefront is installed and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience. + await blogModuleService.deletePosts(post.id) + } +) -The Next.js Starter storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience. +export const createPostWorkflow = createWorkflow( + "create-post", + (postInput: CreatePostWorkflowInput) => { + const post = createPostStep(postInput) -In this chapter, you’ll learn how to install the Next.js Starter storefront separately from the Medusa application. You can also install it while installing the Medusa application as explained in [the installation chapter](https://docs.medusajs.com/learn/installation/index.html.md). + return new WorkflowResponse(post) + } +) +``` -## Install Next.js Starter +The workflow has a single step `createPostStep` that creates a post. In the step, you resolve the Blog Module's service from the Medusa container, which the step receives as a parameter. Then, you create the post using the method `createPosts` of the service, which was generated by `MedusaService`. -### Prerequisites +The step also has a compensation function, which is a function passed as a third-parameter to `createStep` that implements the logic to rollback the change made by a step in case an error occurs during the workflow's execution. -- [Node.js v20+](https://nodejs.org/en/download) -- [Git CLI tool](https://git-scm.com/downloads) +You'll now execute that workflow in an API route to expose the feature of creating blog posts to clients. To create an API route, create the file `src/api/blog/posts/route.ts` with the following content: -If you already have a Medusa application installed with at least one region, you can install the Next.js Starter storefront with the following steps: +```ts +import type { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import { + createPostWorkflow, +} from "../../../workflows/create-post" -1. Clone the [Next.js Starter](https://github.com/medusajs/nextjs-starter-medusa): +export async function POST( + req: MedusaRequest, + res: MedusaResponse +) { + const { result: post } = await createPostWorkflow(req.scope) + .run({ + input: { + title: "My Post", + }, + }) -```bash -git clone https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront + res.json({ + post, + }) +} ``` -2. Change to the `my-medusa-storefront` directory, install the dependencies, and rename the template environment variable file: +This adds a `POST` API route at `/blog/posts`. In the API route, you execute the `createPostWorkflow` by invoking it, passing it the Medusa container in `req.scope`, then invoking the `run` method. In the `run` method, you pass the workflow's input in the `input` property. + +To test this out, start the Medusa application: ```bash npm2yarn -cd my-medusa-storefront -npm install -mv .env.template .env.local +npm run dev ``` -3. Set the Medusa application's publishable API key in the `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` environment variable. You can retrieve the publishable API key in on the Medusa Admin dashboard by going to Settings -> Publishable API Keys +Then, send a `POST` request to `/blog/posts`: ```bash -NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_123... +curl -X POST http://localhost:9000/blog/posts ``` -4. While the Medusa application is running, start the Next.js Starter storefront: +This will create a post and return it in the response: -```bash npm2yarn -npm run dev +```json +{ + "post": { + "id": "123...", + "title": "My Post", + "created_at": "...", + "updated_at": "..." + } +} ``` -Your Next.js Starter storefront is now running at `http://localhost:8000`. - -*** - -## Customize Storefront - -To customize the storefront, refer to the following directories: - -- `src/app`: The storefront’s pages. -- `src/modules`: The storefront’s components. -- `src/styles`: The storefront’s styles. - -You can learn more about development with Next.js through [their documentation](https://nextjs.org/docs/getting-started). - -*** - -## Configurations and Integrations +You can also execute the workflow from a [subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) when an event occurs, or from a [scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) to run it at a specified interval. -The Next.js Starter is compatible with some Medusa integrations out-of-the-box, such as the Stripe provider module. You can also change some of its configurations if necessary. -Refer to the [Next.js Starter reference](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) for more details. +# Scheduled Jobs +In this chapter, you’ll learn about scheduled jobs and how to use them. -# Logging +## What is a Scheduled Job? -In this chapter, you’ll learn how to use Medusa’s logging utility. +When building your commerce application, you may need to automate tasks and run them repeatedly at a specific schedule. For example, you need to automatically sync products to a third-party service once a day. -## Logger Class +In other commerce platforms, this feature isn't natively supported. Instead, you have to setup a separate application to execute cron jobs, which adds complexity as to how you expose this task to be executed in a cron job, or how do you debug it when it's not running within the platform's tooling. -Medusa provides a `Logger` class with advanced logging functionalities. This includes configuring logging levels or saving logs to a file. +Medusa removes this overhead by supporting this feature natively with scheduled jobs. A scheduled job is an asynchronous function that the Medusa application runs at the interval you specify during the Medusa application's runtime. Your efforts are only spent on implementing the functionality performed by the job, such as syncing products to an ERP. -The Medusa application registers the `Logger` class in the Medusa container and each module's container as `logger`. +- You want the action to execute at a specified schedule while the Medusa application **isn't** running. Instead, use the operating system's equivalent of a cron job. +- You want to execute the action once when the application loads. Use [loaders](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md) instead. +- You want to execute the action if an event occurs. Use [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) instead. *** -## How to Log a Message +## How to Create a Scheduled Job? -Resolve the `logger` using the Medusa container to log a message in your resource. +You create a scheduled job in a TypeScript or JavaScript file under the `src/jobs` directory. The file exports the asynchronous function to run, and the configurations indicating the schedule to run the function. -For example, create the file `src/jobs/log-message.ts` with the following content: +For example, create the file `src/jobs/hello-world.ts` with the following content: -```ts title="src/jobs/log-message.ts" highlights={highlights} +![Example of scheduled job file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732866423/Medusa%20Book/scheduled-job-dir-overview_ediqgm.jpg) + +```ts title="src/jobs/hello-world.ts" highlights={highlights} import { MedusaContainer } from "@medusajs/framework/types" -import { ContainerRegistrationKeys } from "@medusajs/framework/utils" -export default async function myCustomJob( - container: MedusaContainer -) { - const logger = container.resolve(ContainerRegistrationKeys.LOGGER) +export default async function greetingJob(container: MedusaContainer) { + const logger = container.resolve("logger") - logger.info("I'm using the logger!") + logger.info("Greeting!") } export const config = { - name: "test-logger", - // execute every minute + name: "greeting-every-minute", schedule: "* * * * *", } ``` -This creates a scheduled job that resolves the `logger` from the Medusa container and uses it to log a message. +You export an asynchronous function that receives the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. In the function, you resolve the [Logger utility](https://docs.medusajs.com/learn/debugging-and-testing/logging/index.html.md) from the Medusa container and log a message. + +You also export a `config` object that has the following properties: + +- `name`: A unique name for the job. +- `schedule`: A string that holds a [cron expression](https://crontab.guru/) indicating the schedule to run the job. + +This scheduled job executes every minute and logs into the terminal `Greeting!`. ### Test the Scheduled Job -To test out the above scheduled job, start the Medusa application: +To test out your scheduled job, start the Medusa application: ```bash npm2yarn npm run dev ``` -After a minute, you'll see the following message as part of the logged messages: +After a minute, the following message will be logged to the terminal: -```text -info: I'm using the logger! +```bash +info: Greeting! ``` *** -## Log Levels - -The `Logger` class has the following methods: - -- `info`: The message is logged with level `info`. -- `warn`: The message is logged with level `warn`. -- `error`: The message is logged with level `error`. -- `debug`: The message is logged with level `debug`. +## Example: Sync Products Once a Day -Each of these methods accepts a string parameter to log in the terminal with the associated level. +In this section, you'll find a brief example of how you use a scheduled job to sync products to a third-party service. -*** +When implementing flows spanning across systems or [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), you use [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). A workflow is a task made up of a series of steps, and you construct it like you would a regular function, but it's a special function that supports rollback mechanism in case of errors, background execution, and more. -## Logging Configurations +You can learn how to create a workflow in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md), but this example assumes you already have a `syncProductToErpWorkflow` implemented. To execute this workflow once a day, create a scheduled job at `src/jobs/sync-products.ts` with the following content: -### Log Level +```ts title="src/jobs/sync-products.ts" +import { MedusaContainer } from "@medusajs/framework/types" +import { syncProductToErpWorkflow } from "../workflows/sync-products-to-erp" -The available log levels, from lowest to highest levels, are: +export default async function syncProductsJob(container: MedusaContainer) { + await syncProductToErpWorkflow(container) + .run() +} -1. `silly` (default, meaning messages of all levels are logged) -2. `debug` -3. `info` -4. `warn` -5. `error` +export const config = { + name: "sync-products-job", + schedule: "0 0 * * *", +} +``` -You can change that by setting the `LOG_LEVEL` environment variable to the minimum level you want to be logged. +In the scheduled job function, you execute the `syncProductToErpWorkflow` by invoking it and passing it the container, then invoking the `run` method. You also specify in the exported configurations the schedule `0 0 * * *` which indicates midnight time of every day. -For example: +The next time you start the Medusa application, it will run this job every day at midnight. -```bash -LOG_LEVEL=error -``` -This logs `error` messages only. +# Workflows -The environment variable must be set as a system environment variable and not in `.env`. +In this chapter, you’ll learn about workflows and how to define and execute them. -### Save Logs in a File +## What is a Workflow? -Aside from showing the logs in the terminal, you can save the logs in a file by setting the `LOG_FILE` environment variable to the path of the file relative to the Medusa server’s root directory. +In digital commerce you typically have many systems involved in your operations. For example, you may have an ERP system that holds product master data and accounting reports, a CMS system for content, a CRM system for managing customer campaigns, a payment service to process credit cards, and so on. Sometimes you may even have custom built applications that need to participate in the commerce stack. One of the biggest challenges when operating a stack like this is ensuring consistency in the data spread across systems. -For example: +Medusa has a built-in durable execution engine to help complete tasks that span multiple systems. You orchestrate your operations across systems in Medusa instead of having to manage it yourself. Other commerce platforms don't have this capability, which makes them a bottleneck to building customizations and iterating quickly. -```bash -LOG_FILE=all.log -``` +A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow similar to how you create a JavaScript function. -Your logs are now saved in the `all.log` file at the root of your Medusa application. +However, unlike regular functions, workflows: -The environment variable must be set as a system environment variable and not in `.env`. +- Create an internal representation of your steps, allowing you to track them and their progress. +- Support defining roll-back logic for each step, so that you can handle errors gracefully and your data remain consistent across systems. +- Perform long actions asynchronously, giving you control over when a step starts and finishes. -*** +You implement all custom flows within workflows, then execute them from [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md), [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md), and [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md). -## Show Log with Progress +*** -The `Logger` class has an `activity` method used to log a message of level `info`. If the Medusa application is running in a development environment, a spinner starts to show the activity's progress. +## How to Create and Execute a Workflow? -For example: +### 1. Create the Steps -```ts title="src/jobs/log-message.ts" -import { MedusaContainer } from "@medusajs/framework/types" -import { ContainerRegistrationKeys } from "@medusajs/framework/utils" +A workflow is made of a series of steps. A step is created using `createStep` from the Workflows SDK. -export default async function myCustomJob( - container: MedusaContainer -) { - const logger = container.resolve(ContainerRegistrationKeys.LOGGER) +Create the file `src/workflows/hello-world.ts` with the following content: - const activityId = logger.activity("First log message") +![Example of workflow file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732866980/Medusa%20Book/workflow-dir-overview_xklukj.jpg) - logger.progress(activityId, `Second log message`) +```ts title="src/workflows/hello-world.ts" highlights={step1Highlights} +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" - logger.success(activityId, "Last log message") -} +const step1 = createStep( + "step-1", + async () => { + return new StepResponse(`Hello from step one!`) + } +) ``` -The `activity` method returns the ID of the started activity. This ID can then be passed to one of the following methods of the `Logger` class: +The `createStep` function accepts the step's unique name as a first parameter, and the step's function as a second parameter. -- `progress`: Log a message of level `info` that indicates progress within that same activity. -- `success`: Log a message of level `info` that indicates that the activity has succeeded. This also ends the associated activity. -- `failure`: Log a message of level `error` that indicates that the activity has failed. This also ends the associated activity. +Steps must return an instance of `StepResponse`, whose parameter is the data to return to the workflow executing the step. -If you configured the `LOG_LEVEL` environment variable to a level higher than those associated with the above methods, their messages won’t be logged. +Steps can accept input parameters. For example, add the following to `src/workflows/hello-world.ts`: +```ts title="src/workflows/hello-world.ts" highlights={step2Highlights} +type WorkflowInput = { + name: string +} -# Configure Instrumentation +const step2 = createStep( + "step-2", + async ({ name }: WorkflowInput) => { + return new StepResponse(`Hello ${name} from step two!`) + } +) +``` -In this chapter, you'll learn about observability in Medusa and how to configure instrumentation with OpenTelemetry. +This adds another step whose function accepts as a parameter an object with a `name` property. -## Observability with OpenTelemtry +### 2. Create a Workflow -Medusa uses [OpenTelemetry](https://opentelemetry.io/) for instrumentation and reporting. When configured, it reports traces for: +Next, add the following to the same file to create the workflow using the `createWorkflow` function: -- HTTP requests -- Workflow executions -- Query usages -- Database queries and operations +```ts title="src/workflows/hello-world.ts" highlights={workflowHighlights} +import { + // other imports... + createWorkflow, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" -*** +// ... -## How to Configure Instrumentation in Medusa? +const myWorkflow = createWorkflow( + "hello-world", + function (input: WorkflowInput) { + const str1 = step1() + // to pass input + const str2 = step2(input) -### Prerequisites + return new WorkflowResponse({ + message: str2, + }) + } +) -- [An exporter to visualize your application's traces, such as Zipkin.](https://zipkin.io/pages/quickstart.html) +export default myWorkflow +``` -### Install Dependencies +The `createWorkflow` function accepts the workflow's unique name as a first parameter, and the workflow's function as a second parameter. The workflow can accept input which is passed as a parameter to the function. -Start by installing the following OpenTelemetry dependencies in your Medusa project: +The workflow must return an instance of `WorkflowResponse`, whose parameter is returned to workflow executors. -```bash npm2yarn -npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg -``` +### 3. Execute the Workflow -Also, install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies: +You can execute a workflow from different customizations: -```bash npm2yarn -npm install @opentelemetry/exporter-zipkin -``` +- Execute in an API route to expose the workflow's functionalities to clients. +- Execute in a subscriber to use the workflow's functionalities when a commerce operation is performed. +- Execute in a scheduled job to run the workflow's functionalities automatically at a specified repeated interval. -### Add instrumentation.ts +To execute the workflow, invoke it passing the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) as a parameter. Then, use its `run` method: -Next, create the file `instrumentation.ts` with the following content: +### API Route -```ts title="instrumentation.ts" -import { registerOtel } from "@medusajs/medusa" -import { ZipkinExporter } from "@opentelemetry/exporter-zipkin" +```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import type { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import myWorkflow from "../../workflows/hello-world" -// If using an exporter other than Zipkin, initialize it here. -const exporter = new ZipkinExporter({ - serviceName: "my-medusa-project", -}) +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const { result } = await myWorkflow(req.scope) + .run({ + input: { + name: "John", + }, + }) -export function register() { - registerOtel({ - serviceName: "medusajs", - // pass exporter - exporter, - instrument: { - http: true, - workflows: true, - query: true, - }, - }) + res.send(result) } ``` -In the `instrumentation.ts` file, you export a `register` function that uses Medusa's `registerOtel` utility function. You also initialize an instance of the exporter, such as Zipkin, and pass it to the `registerOtel` function. +### Subscriber -`registerOtel` accepts an object where you can pass any [NodeSDKConfiguration](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_sdk_node.NodeSDKConfiguration.html) property along with the following properties: +```ts title="src/subscribers/order-placed.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import { + type SubscriberConfig, + type SubscriberArgs, +} from "@medusajs/framework" +import myWorkflow from "../workflows/hello-world" -The `NodeSDKConfiguration` properties are accepted since Medusa v2.5.1. +export default async function handleOrderPlaced({ + event: { data }, + container, +}: SubscriberArgs<{ id: string }>) { + const { result } = await myWorkflow(container) + .run({ + input: { + name: "John", + }, + }) -- serviceName: (\`string\`) The name of the service traced. -- exporter: (\[SpanExporter]\(https://open-telemetry.github.io/opentelemetry-js/interfaces/\_opentelemetry\_sdk\_trace\_base.SpanExporter.html)) An instance of an exporter, such as Zipkin. -- instrument: (\`object\`) Options specifying what to trace. + console.log(result) +} - - http: (\`boolean\`) Whether to trace HTTP requests. +export const config: SubscriberConfig = { + event: "order.placed", +} +``` - - query: (\`boolean\`) Whether to trace Query usages. +### Scheduled Job - - workflows: (\`boolean\`) Whether to trace Workflow executions. +```ts title="src/jobs/message-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"]]} +import { MedusaContainer } from "@medusajs/framework/types" +import myWorkflow from "../workflows/hello-world" - - db: (\`boolean\`) Whether to trace database queries and operations. -- instrumentations: (\[Instrumentation\[]]\(https://open-telemetry.github.io/opentelemetry-js/interfaces/\_opentelemetry\_instrumentation.Instrumentation.html)) Additional instrumentation options that OpenTelemetry accepts. +export default async function myCustomJob( + container: MedusaContainer +) { + const { result } = await myWorkflow(container) + .run({ + input: { + name: "John", + }, + }) -*** + console.log(result.message) +} -## Test it Out +export const config = { + name: "run-once-a-day", + schedule: `0 0 * * *`, +}; +``` -To test it out, start your exporter, such as Zipkin. +### 4. Test Workflow -Then, start your Medusa application: +To test out your workflow, start your Medusa application: ```bash npm2yarn npm run dev ``` -Try to open the Medusa Admin or send a request to an API route. +Then, if you added the API route above, send a `GET` request to `/workflow`: -If you check traces in your exporter, you'll find new traces reported. +```bash +curl http://localhost:9000/workflow +``` -### Trace Span Names +You’ll receive the following response: -Trace span names start with the following keywords based on what it's reporting: +```json title="Example Response" +{ + "message": "Hello John from step two!" +} +``` -- `{methodName} {URL}` when reporting HTTP requests, where `{methodName}` is the HTTP method, and `{URL}` is the URL the request is sent to. -- `route:` when reporting route handlers running on an HTTP request. -- `middleware:` when reporting a middleware running on an HTTP request. -- `workflow:` when reporting a workflow execution. -- `step:` when reporting a step in a workflow execution. -- `query.graph:` when reporting Query usages. -- `pg.query:` when reporting database queries and operations. +*** +## Access Medusa Container in Workflow Steps -# Medusa Testing Tools +A step receives an object as a second parameter with configurations and context-related properties. One of these properties is the `container` property, which is the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) to allow you to resolve framework and commerce tools in your application. -In this chapter, you'll learn about Medusa's testing tools and how to install and configure them. +For example, consider you want to implement a workflow that returns the total products in your application. Create the file `src/workflows/product-count.ts` with the following content: -## @medusajs/test-utils Package +```ts title="src/workflows/product-count.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports" +import { + createStep, + StepResponse, + createWorkflow, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" -Medusa provides a Testing Framework to create integration tests for your custom API routes, modules, or other Medusa customizations. +const getProductCountStep = createStep( + "get-product-count", + async (_, { container }) => { + const productModuleService = container.resolve("product") -To use the Testing Framework, install `@medusajs/test-utils` as a `devDependency`: + const [, count] = await productModuleService.listAndCountProducts() -```bash npm2yarn -npm install --save-dev @medusajs/test-utils@latest + return new StepResponse(count) + } +) + +const productCountWorkflow = createWorkflow( + "product-count", + function () { + const count = getProductCountStep() + + return new WorkflowResponse({ + count, + }) + } +) + +export default productCountWorkflow ``` +In `getProductCountStep`, you use the `container` to resolve the Product Module's main service. Then, you use its `listAndCountProducts` method to retrieve the total count of products and return it in the step's response. You then execute this step in the `productCountWorkflow`. + +You can now execute this workflow in a custom API route, scheduled job, or subscriber to get the total count of products. + +Find a full list of the registered resources in the Medusa container and their registration key in [this reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md). You can use these resources in your custom workflows. + + +# Medusa's Architecture + +In this chapter, you'll learn about the architectural layers in Medusa. + +## HTTP, Workflow, and Module Layers + +Medusa is a headless commerce platform. So, storefronts, admin dashboards, and other clients consume Medusa's functionalities through its API routes. + +In a common Medusa application, requests go through four layers in the stack. In order of entry, those are: + +1. API Routes (HTTP): Our API Routes are the typical entry point. +2. Workflows: API Routes consume workflows that hold the opinionated business logic of your application. +3. Modules: Workflows use domain-specific modules for resource management. +4. Data store: Modules query the underlying datastore, which is a PostgreSQL database in common cases. + +These layers of stack can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). + +![Diagram illustrating the HTTP layer](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175296/Medusa%20Book/http-layer_sroafr.jpg) + *** -## Install and Configure Jest +## Database Layer -Writing tests with `@medusajs/test-utils`'s tools requires installing and configuring Jest in your project. +The Medusa application injects into each module a connection to the configured PostgreSQL database. Modules use that connection to read and write data to the database. -Run the following command to install the required Jest dependencies: +Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). -```bash npm2yarn -npm install --save-dev jest @types/jest @swc/jest -``` +![Diagram illustrating the database layer](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175379/Medusa%20Book/db-layer_pi7tix.jpg) -Then, create the file `jest.config.js` with the following content: +*** -```js title="jest.config.js" -const { loadEnv } = require("@medusajs/framework/utils") -loadEnv("test", process.cwd()) +## Service Integrations -module.exports = { - transform: { - "^.+\\.[jt]s$": [ - "@swc/jest", - { - jsc: { - parser: { syntax: "typescript", decorators: true }, - }, - }, - ], - }, - testEnvironment: "node", - moduleFileExtensions: ["js", "ts", "json"], - modulePathIgnorePatterns: ["dist/"], - setupFiles: ["./integration-tests/setup.js"], -} +Third-party services are integrated through commerce and architectural modules. You also create custom third-party integrations through a custom module. -if (process.env.TEST_TYPE === "integration:http") { - module.exports.testMatch = ["**/integration-tests/http/*.spec.[jt]s"] -} else if (process.env.TEST_TYPE === "integration:modules") { - module.exports.testMatch = ["**/src/modules/*/__tests__/**/*.[jt]s"] -} else if (process.env.TEST_TYPE === "unit") { - module.exports.testMatch = ["**/src/**/__tests__/**/*.unit.spec.[jt]s"] -} -``` +Modules can be implemented within [plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md). -Next, create the `integration-tests/setup.js` file with the following content: +### Commerce Modules -```js title="integration-tests/setup.js" -const { MetadataStorage } = require("@mikro-orm/core") +[Commerce modules](https://docs.medusajs.com/resources/commerce-modules/index.html.md) integrate third-party services relevant for commerce or user-facing features. For example, you integrate Stripe through a payment module provider. -MetadataStorage.clear() -``` +![Diagram illustrating the commerce modules integration to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175357/Medusa%20Book/service-commerce_qcbdsl.jpg) + +### Architectural Modules + +[Architectural modules](https://docs.medusajs.com/resources/architectural-modules/index.html.md) integrate third-party services and systems for architectural features. For example, you integrate Redis as a pub/sub service to send events, or SendGrid to send notifications. + +![Diagram illustrating the architectural modules integration to third-party services and systems](https://res.cloudinary.com/dza7lstvk/image/upload/v1727175342/Medusa%20Book/service-arch_ozvryw.jpg) *** -## Add Test Commands +## Full Diagram of Medusa's Architecture -Finally, add the following scripts to `package.json`: +The following diagram illustrates Medusa's architecture over the three layers. -```json title="package.json" -"scripts": { - // ... - "test:integration:http": "TEST_TYPE=integration:http NODE_OPTIONS=--experimental-vm-modules jest --silent=false --runInBand --forceExit", - "test:integration:modules": "TEST_TYPE=integration:modules NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit", - "test:unit": "TEST_TYPE=unit NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit" -}, +![Full diagram illustrating Medusa's architecture](https://res.cloudinary.com/dza7lstvk/image/upload/v1727174897/Medusa%20Book/architectural-diagram-full.jpg) + + +# Next.js Starter Storefront + +The Medusa application is made up of a Node.js server and an admin dashboard. The storefront is installed and hosted separately from the Medusa application, giving you the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience. + +The Next.js Starter storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience. + +In this chapter, you’ll learn how to install the Next.js Starter storefront separately from the Medusa application. You can also install it while installing the Medusa application as explained in [the installation chapter](https://docs.medusajs.com/learn/installation/index.html.md). + +## Install Next.js Starter + +### Prerequisites + +- [Node.js v20+](https://nodejs.org/en/download) +- [Git CLI tool](https://git-scm.com/downloads) + +If you already have a Medusa application installed with at least one region, you can install the Next.js Starter storefront with the following steps: + +1. Clone the [Next.js Starter](https://github.com/medusajs/nextjs-starter-medusa): + +```bash +git clone https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront ``` -You now have two commands: +2. Change to the `my-medusa-storefront` directory, install the dependencies, and rename the template environment variable file: -- `test:integration:http` to run integration tests (for example, for API routes and workflows) available under the `integration-tests/http` directory. -- `test:integration:modules` to run integration tests for modules available in any `__tests__` directory under `src/modules`. -- `test:unit` to run unit tests in any `__tests__` directory under the `src` directory. +```bash npm2yarn +cd my-medusa-storefront +npm install +mv .env.template .env.local +``` -Medusa's Testing Framework works for integration tests only. You can write unit tests using Jest. +3. Set the Medusa application's publishable API key in the `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` environment variable. You can retrieve the publishable API key in on the Medusa Admin dashboard by going to Settings -> Publishable API Keys + +```bash +NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_123... +``` + +4. While the Medusa application is running, start the Next.js Starter storefront: + +```bash npm2yarn +npm run dev +``` + +Your Next.js Starter storefront is now running at `http://localhost:8000`. *** -## Test Tools and Writing Tests +## Customize Storefront -The next chapters explain how to use the testing tools provided by `@medusajs/test-utils` to write tests. +To customize the storefront, refer to the following directories: + +- `src/app`: The storefront’s pages. +- `src/modules`: The storefront’s components. +- `src/styles`: The storefront’s styles. + +You can learn more about development with Next.js through [their documentation](https://nextjs.org/docs/getting-started). + +*** + +## Configurations and Integrations + +The Next.js Starter is compatible with some Medusa integrations out-of-the-box, such as the Stripe provider module. You can also change some of its configurations if necessary. + +Refer to the [Next.js Starter reference](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) for more details. # Guide: Create Brand API Route @@ -3217,56 +3217,257 @@ You now have a `createBrandWorkflow` that you can execute to create a brand. In the next chapter, you'll add an API route that allows admin users to create a brand. You'll learn how to create the API route, and execute in it the workflow you implemented in this chapter. -# Create Brands UI Route in Admin +# Write Integration Tests -In this chapter, you'll add a UI route to the admin dashboard that shows all [brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) in a new page. You'll retrieve the brands from the server and display them in a table with pagination. +In this chapter, you'll learn about `medusaIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests. ### Prerequisites -- [Brands Module](https://docs.medusajs.com/learn/customization/custom-features/modules/index.html.md) +- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md) -## 1. Get Brands API Route +## medusaIntegrationTestRunner Utility -In a [previous chapter](https://docs.medusajs.com/learn/customization/extend-features/query-linked-records/index.html.md), you learned how to add an API route that retrieves brands and their products using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). You'll expand that API route to support pagination, so that on the admin dashboard you can show the brands in a paginated table. +The `medusaIntegrationTestRunner` is from Medusa's Testing Framework and it's used to create integration tests in your Medusa project. It runs a full Medusa application, allowing you test API routes, workflows, or other customizations. -Replace or create the `GET` API route at `src/api/admin/brands/route.ts` with the following: +For example: -```ts title="src/api/admin/brands/route.ts" highlights={apiRouteHighlights} -// other imports... -import { - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" +```ts title="integration-tests/http/test.spec.ts" highlights={highlights} +import { medusaIntegrationTestRunner } from "@medusajs/test-utils" -export const GET = async ( - req: MedusaRequest, - res: MedusaResponse -) => { - const query = req.scope.resolve("query") - - const { - data: brands, - metadata: { count, take, skip } = {}, - } = await query.graph({ - entity: "brand", - ...req.queryConfig, - }) +medusaIntegrationTestRunner({ + testSuite: ({ api, getContainer }) => { + // TODO write tests... + }, +}) - res.json({ - brands, - count, - limit: take, - offset: skip, - }) -} +jest.setTimeout(60 * 1000) ``` -In the API route, you use Query's `graph` method to retrieve the brands. In the method's object parameter, you spread the `queryConfig` property of the request object. This property holds configurations for pagination and retrieved fields. +The `medusaIntegrationTestRunner` function accepts an object as a parameter. The object has a required property `testSuite`. -The query configurations are combined from default configurations, which you'll add next, and the request's query parameters: +`testSuite`'s value is a function that defines the tests to run. The function accepts as a parameter an object that has the following properties: -- `fields`: The fields to retrieve in the brands. -- `limit`: The maximum number of items to retrieve. +- `api`: a set of utility methods used to send requests to the Medusa application. It has the following methods: + - `get`: Send a `GET` request to an API route. + - `post`: Send a `POST` request to an API route. + - `delete`: Send a `DELETE` request to an API route. +- `getContainer`: a function that retrieves the Medusa Container. Use the `getContainer().resolve` method to resolve resources from the Medusa Container. + +The tests in the `testSuite` function are written using [Jest](https://jestjs.io/). + +### Jest Timeout + +Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test: + +```ts title="integration-tests/http/test.spec.ts" +// in your test's file +jest.setTimeout(60 * 1000) +``` + +*** + +### Run Tests + +Run the following command to run your tests: + +```bash npm2yarn +npm run test:integration +``` + +If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md). + +This runs your Medusa application and runs the tests available under the `src/integrations/http` directory. + +*** + +## Other Options and Inputs + +Refer to [this reference in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function. + +*** + +## Database Used in Tests + +The `medusaIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end. + +To manage that database, such as changing its name or perform operations on it in your tests, refer to the [references in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md). + +*** + +## Example Integration Tests + +The next chapters provide examples of writing integration tests for API routes and workflows. + + +# Write Tests for Modules + +In this chapter, you'll learn about `moduleIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests for a module's main service. + +### Prerequisites + +- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md) + +## moduleIntegrationTestRunner Utility + +`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled. + +For example, assuming you have a `hello` module, create a test file at `src/modules/hello/__tests__/service.spec.ts`: + +```ts title="src/modules/hello/__tests__/service.spec.ts" +import { moduleIntegrationTestRunner } from "@medusajs/test-utils" +import { HELLO_MODULE } from ".." +import HelloModuleService from "../service" +import MyCustom from "../models/my-custom" + +moduleIntegrationTestRunner({ + moduleName: HELLO_MODULE, + moduleModels: [MyCustom], + resolve: "./src/modules/hello", + testSuite: ({ service }) => { + // TODO write tests + }, +}) + +jest.setTimeout(60 * 1000) +``` + +The `moduleIntegrationTestRunner` function accepts as a parameter an object with the following properties: + +- `moduleName`: The name of the module. +- `moduleModels`: An array of models in the module. Refer to [this section](#write-tests-for-modules-without-data-models) if your module doesn't have data models. +- `resolve`: The path to the model. +- `testSuite`: A function that defines the tests to run. + +The `testSuite` function accepts as a parameter an object having the `service` property, which is an instance of the module's main service. + +The type argument provided to the `moduleIntegrationTestRunner` function is used as the type of the `service` property. + +The tests in the `testSuite` function are written using [Jest](https://jestjs.io/). + +*** + +## Run Tests + +Run the following command to run your module integration tests: + +```bash npm2yarn +npm run test:integration:modules +``` + +If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md). + +This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory. + +*** + +## Pass Module Options + +If your module accepts options, you can set them using the `moduleOptions` property of the `moduleIntegrationTestRunner`'s parameter. + +For example: + +```ts +import { moduleIntegrationTestRunner } from "@medusajs/test-utils" +import HelloModuleService from "../service" + +moduleIntegrationTestRunner({ + moduleOptions: { + apiKey: "123", + }, + // ... +}) +``` + +*** + +## Write Tests for Modules without Data Models + +If your module doesn't have a data model, pass a dummy model in the `moduleModels` property. + +For example: + +```ts +import { moduleIntegrationTestRunner } from "@medusajs/test-utils" +import HelloModuleService from "../service" +import { model } from "@medusajs/framework/utils" + +const DummyModel = model.define("dummy_model", { + id: model.id().primaryKey(), +}) + +moduleIntegrationTestRunner({ + moduleModels: [DummyModel], + // ... +}) + +jest.setTimeout(60 * 1000) +``` + +*** + +### Other Options and Inputs + +Refer to [this reference in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function. + +*** + +## Database Used in Tests + +The `moduleIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end. + +To manage that database, such as changing its name or perform operations on it in your tests, refer to the [references in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md). + + +# Create Brands UI Route in Admin + +In this chapter, you'll add a UI route to the admin dashboard that shows all [brands](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) in a new page. You'll retrieve the brands from the server and display them in a table with pagination. + +### Prerequisites + +- [Brands Module](https://docs.medusajs.com/learn/customization/custom-features/modules/index.html.md) + +## 1. Get Brands API Route + +In a [previous chapter](https://docs.medusajs.com/learn/customization/extend-features/query-linked-records/index.html.md), you learned how to add an API route that retrieves brands and their products using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md). You'll expand that API route to support pagination, so that on the admin dashboard you can show the brands in a paginated table. + +Replace or create the `GET` API route at `src/api/admin/brands/route.ts` with the following: + +```ts title="src/api/admin/brands/route.ts" highlights={apiRouteHighlights} +// other imports... +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const query = req.scope.resolve("query") + + const { + data: brands, + metadata: { count, take, skip } = {}, + } = await query.graph({ + entity: "brand", + ...req.queryConfig, + }) + + res.json({ + brands, + count, + limit: take, + offset: skip, + }) +} +``` + +In the API route, you use Query's `graph` method to retrieve the brands. In the method's object parameter, you spread the `queryConfig` property of the request object. This property holds configurations for pagination and retrieved fields. + +The query configurations are combined from default configurations, which you'll add next, and the request's query parameters: + +- `fields`: The fields to retrieve in the brands. +- `limit`: The maximum number of items to retrieve. - `offset`: The number of items to skip before retrieving the returned items. When you pass pagination configurations to the `graph` method, the returned object has the pagination's details in a `metadata` property, whose value is an object having the following properties: @@ -3589,41 +3790,111 @@ Your customizations often span across systems, where you need to retrieve data o In the next chapters, you'll learn about the concepts that facilitate integrating third-party systems in your application. You'll integrate a dummy third-party system and sync the brands between it and the Medusa application. -# Guide: Add Product's Brand Widget in Admin +# Guide: Define Module Link Between Brand and Product -In this chapter, you'll customize the product details page of the Medusa Admin dashboard to show the product's [brand](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md). You'll create a widget that is injected into a pre-defined zone in the page, and in the widget you'll retrieve the product's brand from the server and display it. +In this chapter, you'll learn how to define a module link between a brand defined in the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), and a product defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) that's available in your Medusa application out-of-the-box. -### Prerequisites +Modules are [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) from other resources, ensuring that they're integrated into the Medusa application without side effects. However, you may need to associate data models of different modules, or you're trying to extend data models from commerce modules with custom properties. To do that, you define module links. -- [Brands linked to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) +A module link forms an association between two data models of different modules while maintaining module isolation. You can then manage and query linked records of the data models using Medusa's Modules SDK. -## 1. Initialize JS SDK +In this chapter, you'll define a module link between the `Brand` data model of the Brand Module, and the `Product` data model of the Product Module. In later chapters, you'll manage and retrieve linked product and brand records. -In your custom widget, you'll retrieve the product's brand by sending a request to the Medusa server. Medusa has a [JS SDK](https://docs.medusajs.com/resources/js-sdk/index.html.md) that simplifies sending requests to the server's API routes. +Learn more about module links in [this chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md). -So, you'll start by configuring the JS SDK. Create the file `src/admin/lib/sdk.ts` with the following content: +### Prerequisites -![The directory structure of the Medusa application after adding the file](https://res.cloudinary.com/dza7lstvk/image/upload/v1733414606/Medusa%20Book/brands-admin-dir-overview-1_jleg0t.jpg) +- [Brand Module having a Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) -```ts title="src/admin/lib/sdk.ts" -import Medusa from "@medusajs/js-sdk" +## 1. Define Link -export const sdk = new Medusa({ - baseUrl: import.meta.env.VITE_BACKEND_URL || "/", - debug: import.meta.env.DEV, - auth: { - type: "session", - }, -}) -``` +Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using `defineLink` from the Modules SDK. -You initialize the SDK passing it the following options: +So, to define a link between the `Product` and `Brand` models, create the file `src/links/product-brand.ts` with the following content: -- `baseUrl`: The URL to the Medusa server. -- `debug`: Whether to enable logging debug messages. This should only be enabled in development. -- `auth.type`: The authentication method used in the client application, which is `session` in the Medusa Admin dashboard. +![The directory structure of the Medusa application after adding the link.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733329897/Medusa%20Book/brands-link-dir-overview_t1rhlp.jpg) -Notice that you use `import.meta.env` to access environment variables in your customizations because the Medusa Admin is built on top of Vite. Learn more in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md). +```ts title="src/links/product-brand.ts" highlights={highlights} +import BrandModule from "../modules/brand" +import ProductModule from "@medusajs/medusa/product" +import { defineLink } from "@medusajs/framework/utils" + +export default defineLink( + { + linkable: ProductModule.linkable.product, + isList: true, + }, + BrandModule.linkable.brand +) +``` + +You import each module's definition object from the `index.ts` file of the module's directory. Each module object has a special `linkable` property that holds the data models' link configurations. + +The `defineLink` function accepts two parameters of the same type, which is either: + +- The data model's link configuration, which you access from the Module's `linkable` property; +- Or an object that has two properties: + - `linkable`: the data model's link configuration, which you access from the Module's `linkable` property. + - `isList`: A boolean indicating whether many records of the data model can be linked to the other model. + +So, in the above code snippet, you define a link between the `Product` and `Brand` data models. Since a brand can be associated with multiple products, you enable `isList` in the `Product` model's object. + +*** + +## 2. Sync the Link to the Database + +A module link is represented in the database as a table that stores the IDs of linked records. So, after defining the link, run the following command to create the module link's table in the database: + +```bash +npx medusa db:migrate +``` + +This command reflects migrations on the database and syncs module links, which creates a table for the `product-brand` link. + +You can also run the `npx medusa db:sync-links` to just sync module links without running migrations. + +*** + +## Next Steps: Extend Create Product Flow + +In the next chapter, you'll extend Medusa's workflow and API route that create a product to allow associating a brand with a product. You'll also learn how to link brand and product records. + + +# Guide: Add Product's Brand Widget in Admin + +In this chapter, you'll customize the product details page of the Medusa Admin dashboard to show the product's [brand](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md). You'll create a widget that is injected into a pre-defined zone in the page, and in the widget you'll retrieve the product's brand from the server and display it. + +### Prerequisites + +- [Brands linked to products](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) + +## 1. Initialize JS SDK + +In your custom widget, you'll retrieve the product's brand by sending a request to the Medusa server. Medusa has a [JS SDK](https://docs.medusajs.com/resources/js-sdk/index.html.md) that simplifies sending requests to the server's API routes. + +So, you'll start by configuring the JS SDK. Create the file `src/admin/lib/sdk.ts` with the following content: + +![The directory structure of the Medusa application after adding the file](https://res.cloudinary.com/dza7lstvk/image/upload/v1733414606/Medusa%20Book/brands-admin-dir-overview-1_jleg0t.jpg) + +```ts title="src/admin/lib/sdk.ts" +import Medusa from "@medusajs/js-sdk" + +export const sdk = new Medusa({ + baseUrl: import.meta.env.VITE_BACKEND_URL || "/", + debug: import.meta.env.DEV, + auth: { + type: "session", + }, +}) +``` + +You initialize the SDK passing it the following options: + +- `baseUrl`: The URL to the Medusa server. +- `debug`: Whether to enable logging debug messages. This should only be enabled in development. +- `auth.type`: The authentication method used in the client application, which is `session` in the Medusa Admin dashboard. + +Notice that you use `import.meta.env` to access environment variables in your customizations because the Medusa Admin is built on top of Vite. Learn more in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md). You can now use the SDK to send requests to the Medusa server. @@ -3955,76 +4226,6 @@ In the Medusa application's logs, you'll find the message `Linked brand to produ Now that you've extending the create-product flow to link a brand to it, you want to retrieve the brand details of a product. You'll learn how to do so in the next chapter. -# Guide: Define Module Link Between Brand and Product - -In this chapter, you'll learn how to define a module link between a brand defined in the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md), and a product defined in the [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md) that's available in your Medusa application out-of-the-box. - -Modules are [isolated](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) from other resources, ensuring that they're integrated into the Medusa application without side effects. However, you may need to associate data models of different modules, or you're trying to extend data models from commerce modules with custom properties. To do that, you define module links. - -A module link forms an association between two data models of different modules while maintaining module isolation. You can then manage and query linked records of the data models using Medusa's Modules SDK. - -In this chapter, you'll define a module link between the `Brand` data model of the Brand Module, and the `Product` data model of the Product Module. In later chapters, you'll manage and retrieve linked product and brand records. - -Learn more about module links in [this chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md). - -### Prerequisites - -- [Brand Module having a Brand data model](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) - -## 1. Define Link - -Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using `defineLink` from the Modules SDK. - -So, to define a link between the `Product` and `Brand` models, create the file `src/links/product-brand.ts` with the following content: - -![The directory structure of the Medusa application after adding the link.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733329897/Medusa%20Book/brands-link-dir-overview_t1rhlp.jpg) - -```ts title="src/links/product-brand.ts" highlights={highlights} -import BrandModule from "../modules/brand" -import ProductModule from "@medusajs/medusa/product" -import { defineLink } from "@medusajs/framework/utils" - -export default defineLink( - { - linkable: ProductModule.linkable.product, - isList: true, - }, - BrandModule.linkable.brand -) -``` - -You import each module's definition object from the `index.ts` file of the module's directory. Each module object has a special `linkable` property that holds the data models' link configurations. - -The `defineLink` function accepts two parameters of the same type, which is either: - -- The data model's link configuration, which you access from the Module's `linkable` property; -- Or an object that has two properties: - - `linkable`: the data model's link configuration, which you access from the Module's `linkable` property. - - `isList`: A boolean indicating whether many records of the data model can be linked to the other model. - -So, in the above code snippet, you define a link between the `Product` and `Brand` data models. Since a brand can be associated with multiple products, you enable `isList` in the `Product` model's object. - -*** - -## 2. Sync the Link to the Database - -A module link is represented in the database as a table that stores the IDs of linked records. So, after defining the link, run the following command to create the module link's table in the database: - -```bash -npx medusa db:migrate -``` - -This command reflects migrations on the database and syncs module links, which creates a table for the `product-brand` link. - -You can also run the `npx medusa db:sync-links` to just sync module links without running migrations. - -*** - -## Next Steps: Extend Create Product Flow - -In the next chapter, you'll extend Medusa's workflow and API route that create a product to allow associating a brand with a product. You'll also learn how to link brand and product records. - - # Guide: Query Product's Brands In the previous chapters, you [defined a link](https://docs.medusajs.com/learn/customization/extend-features/define-link/index.html.md) between the [custom Brand Module](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md) and Medusa's [Product Module](https://docs.medusajs.com/resources/commerce-modules/product/index.html.md), then [extended the create-product flow](https://docs.medusajs.com/learn/customization/extend-features/extend-create-product/index.html.md) to link a product to a brand. @@ -4592,195 +4793,81 @@ You can now use the CMS Module's service to perform actions on the third-party C In the next chapter, you'll learn how to emit an event when a brand is created, then handle that event to sync the brand from Medusa to the third-party service. -# Admin Development Constraints +# Guide: Schedule Syncing Brands from CMS -This chapter lists some constraints of admin widgets and UI routes. +In the previous chapters, you've [integrated a third-party CMS](https://docs.medusajs.com/learn/customization/integrate-systems/service/index.html.md) and implemented the logic to [sync created brands](https://docs.medusajs.com/learn/customization/integrate-systems/handle-event/index.html.md) from Medusa to the CMS. -## Arrow Functions +However, when you integrate a third-party system, you want the data to be in sync between the Medusa application and the system. One way to do so is by automatically syncing the data once a day. -Widget and UI route components must be created as arrow functions. +You can create an action to be automatically executed at a specified interval using scheduled jobs. A scheduled job is an asynchronous function with a specified schedule of when the Medusa application should run it. Scheduled jobs are useful to automate repeated tasks. -```ts highlights={arrowHighlights} -// Don't -function ProductWidget() { - // ... -} +Learn more about scheduled jobs in [this chapter](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md). -// Do -const ProductWidget = () => { - // ... -} -``` +In this chapter, you'll create a scheduled job that triggers syncing the brands from the third-party CMS to Medusa once a day. You'll implement the syncing logic in a workflow, and execute that workflow in the scheduled job. + +### Prerequisites + +- [CMS Module](https://docs.medusajs.com/learn/customization/integrate-systems/service/index.html.md) *** -## Widget Zone +## 1. Implement Syncing Workflow -A widget zone's value must be wrapped in double or single quotes. It can't be a template literal or a variable. +You'll start by implementing the syncing logic in a workflow, then execute the workflow later in the scheduled job. -```ts highlights={zoneHighlights} -// Don't -export const config = defineWidgetConfig({ - zone: `product.details.before`, -}) +Workflows have a built-in durable execution engine that helps you complete tasks spanning multiple systems. Also, their rollback mechanism ensures that data is consistent across systems even when errors occur during execution. -// Don't -const ZONE = "product.details.after" -export const config = defineWidgetConfig({ - zone: ZONE, -}) +Learn more about workflows in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). -// Do -export const config = defineWidgetConfig({ - zone: "product.details.before", -}) -``` +This workflow will have three steps: +1. `retrieveBrandsFromCmsStep` to retrieve the brands from the CMS. +2. `createBrandsStep` to create the brands retrieved in the first step that don't exist in Medusa. +3. `updateBrandsStep` to update the brands retrieved in the first step that exist in Medusa. -# Environment Variables in Admin Customizations +### retrieveBrandsFromCmsStep -In this chapter, you'll learn how to use environment variables in your admin customizations. +To create the step that retrieves the brands from the third-party CMS, create the file `src/workflows/sync-brands-from-cms.ts` with the following content: -To learn how envirnment variables are generally loaded in Medusa based on your application's environment, check out [this chapter](https://docs.medusajs.com/learn/fundamentals/environment-variables/index.html.md). +![Directory structure of the Medusa application after creating the file.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733494196/Medusa%20Book/cms-dir-overview-6_z1omsi.jpg) -## How to Set Environment Variables +```ts title="src/workflows/sync-brands-from-cms.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports" +import { + createStep, + StepResponse, +} from "@medusajs/framework/workflows-sdk" +import CmsModuleService from "../modules/cms/service" +import { CMS_MODULE } from "../modules/cms" -The Medusa Admin is built on top of [Vite](https://vite.dev/). To set an environment variable that you want to use in a widget or UI route, prefix the environment variable with `VITE_`. +const retrieveBrandsFromCmsStep = createStep( + "retrieve-brands-from-cms", + async (_, { container }) => { + const cmsModuleService: CmsModuleService = container.resolve( + CMS_MODULE + ) -For example: + const brands = await cmsModuleService.retrieveBrands() -```plain -VITE_MY_API_KEY=sk_123 + return new StepResponse(brands) + } +) ``` -*** +You create a `retrieveBrandsFromCmsStep` that resolves the CMS Module's service and uses its `retrieveBrands` method to retrieve the brands in the CMS. You return those brands in the step's response. -## How to Use Environment Variables +### createBrandsStep -To access or use an environment variable starting with `VITE_`, use the `import.meta.env` object. +The brands retrieved in the first step may have brands that don't exist in Medusa. So, you'll create a step that creates those brands. Add the step to the same `src/workflows/sync-brands-from-cms.ts` file: -For example: +```ts title="src/workflows/sync-brands-from-cms.ts" highlights={createBrandsHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports" +// other imports... +import BrandModuleService from "../modules/brand/service" +import { BRAND_MODULE } from "../modules/brand" -```tsx highlights={[["8"]]} -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { Container, Heading } from "@medusajs/ui" +// ... -const ProductWidget = () => { - return ( - -
- API Key: {import.meta.env.VITE_MY_API_KEY} -
-
- ) -} - -export const config = defineWidgetConfig({ - zone: "product.details.before", -}) - -export default ProductWidget -``` - -In this example, you display the API key in a widget using `import.meta.env.VITE_MY_API_KEY`. - -### Type Error on import.meta.env - -If you receive a type error on `import.meta.env`, create the file `src/admin/vite-env.d.ts` with the following content: - -```ts title="src/admin/vite-env.d.ts" -/// -``` - -This file tells TypeScript to recognize the `import.meta.env` object and enhances the types of your custom environment variables. - -*** - -## Check Node Environment in Admin Customizations - -To check the current environment, Vite exposes two variables: - -- `import.meta.env.DEV`: Returns `true` if the current environment is development. -- `import.meta.env.PROD`: Returns `true` if the current environment is production. - -Learn more about other Vite environment variables in the [Vite documentation](https://vite.dev/guide/env-and-mode). - - -# Guide: Schedule Syncing Brands from CMS - -In the previous chapters, you've [integrated a third-party CMS](https://docs.medusajs.com/learn/customization/integrate-systems/service/index.html.md) and implemented the logic to [sync created brands](https://docs.medusajs.com/learn/customization/integrate-systems/handle-event/index.html.md) from Medusa to the CMS. - -However, when you integrate a third-party system, you want the data to be in sync between the Medusa application and the system. One way to do so is by automatically syncing the data once a day. - -You can create an action to be automatically executed at a specified interval using scheduled jobs. A scheduled job is an asynchronous function with a specified schedule of when the Medusa application should run it. Scheduled jobs are useful to automate repeated tasks. - -Learn more about scheduled jobs in [this chapter](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md). - -In this chapter, you'll create a scheduled job that triggers syncing the brands from the third-party CMS to Medusa once a day. You'll implement the syncing logic in a workflow, and execute that workflow in the scheduled job. - -### Prerequisites - -- [CMS Module](https://docs.medusajs.com/learn/customization/integrate-systems/service/index.html.md) - -*** - -## 1. Implement Syncing Workflow - -You'll start by implementing the syncing logic in a workflow, then execute the workflow later in the scheduled job. - -Workflows have a built-in durable execution engine that helps you complete tasks spanning multiple systems. Also, their rollback mechanism ensures that data is consistent across systems even when errors occur during execution. - -Learn more about workflows in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). - -This workflow will have three steps: - -1. `retrieveBrandsFromCmsStep` to retrieve the brands from the CMS. -2. `createBrandsStep` to create the brands retrieved in the first step that don't exist in Medusa. -3. `updateBrandsStep` to update the brands retrieved in the first step that exist in Medusa. - -### retrieveBrandsFromCmsStep - -To create the step that retrieves the brands from the third-party CMS, create the file `src/workflows/sync-brands-from-cms.ts` with the following content: - -![Directory structure of the Medusa application after creating the file.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733494196/Medusa%20Book/cms-dir-overview-6_z1omsi.jpg) - -```ts title="src/workflows/sync-brands-from-cms.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports" -import { - createStep, - StepResponse, -} from "@medusajs/framework/workflows-sdk" -import CmsModuleService from "../modules/cms/service" -import { CMS_MODULE } from "../modules/cms" - -const retrieveBrandsFromCmsStep = createStep( - "retrieve-brands-from-cms", - async (_, { container }) => { - const cmsModuleService: CmsModuleService = container.resolve( - CMS_MODULE - ) - - const brands = await cmsModuleService.retrieveBrands() - - return new StepResponse(brands) - } -) -``` - -You create a `retrieveBrandsFromCmsStep` that resolves the CMS Module's service and uses its `retrieveBrands` method to retrieve the brands in the CMS. You return those brands in the step's response. - -### createBrandsStep - -The brands retrieved in the first step may have brands that don't exist in Medusa. So, you'll create a step that creates those brands. Add the step to the same `src/workflows/sync-brands-from-cms.ts` file: - -```ts title="src/workflows/sync-brands-from-cms.ts" highlights={createBrandsHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports" -// other imports... -import BrandModuleService from "../modules/brand/service" -import { BRAND_MODULE } from "../modules/brand" - -// ... - -type CreateBrand = { - name: string +type CreateBrand = { + name: string } type CreateBrandsInput = { @@ -5015,101 +5102,85 @@ By following the previous chapters, you utilized Medusa's framework and orchestr With Medusa, you can integrate any service from your commerce ecosystem with ease. You don't have to set up separate applications to manage your different customizations, or worry about data inconsistency across systems. Your efforts only go into implementing the business logic that ties your systems together. -# Admin Development Tips +# Admin Development Constraints -In this chapter, you'll find some tips for your admin development. +This chapter lists some constraints of admin widgets and UI routes. -## Send Requests to API Routes +## Arrow Functions -To send a request to an API route in the Medusa Application, use Medusa's [JS SDK](https://docs.medusajs.com/resources/js-sdk/index.html.md) with [Tanstack Query](https://tanstack.com/query/latest). Both of these tools are installed in your project by default. +Widget and UI route components must be created as arrow functions. -Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency. +```ts highlights={arrowHighlights} +// Don't +function ProductWidget() { + // ... +} -First, create the file `src/admin/lib/config.ts` to setup the SDK for use in your customizations: +// Do +const ProductWidget = () => { + // ... +} +``` -```ts -import Medusa from "@medusajs/js-sdk" +*** -export const sdk = new Medusa({ - baseUrl: import.meta.env.VITE_BACKEND_URL || "/", - debug: import.meta.env.DEV, - auth: { - type: "session", - }, +## Widget Zone + +A widget zone's value must be wrapped in double or single quotes. It can't be a template literal or a variable. + +```ts highlights={zoneHighlights} +// Don't +export const config = defineWidgetConfig({ + zone: `product.details.before`, }) -``` -Notice that you use `import.meta.env` to access environment variables in your customizations, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md). +// Don't +const ZONE = "product.details.after" +export const config = defineWidgetConfig({ + zone: ZONE, +}) -Learn more about the JS SDK's configurations [this documentation](https://docs.medusajs.com/resources/js-sdk#js-sdk-configurations/index.html.md). +// Do +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) +``` -Then, use the configured SDK with the `useQuery` Tanstack Query hook to send `GET` requests, and `useMutation` hook to send `POST` or `DELETE` requests. -For example: +# Environment Variables in Admin Customizations -### Query +In this chapter, you'll learn how to use environment variables in your admin customizations. -```tsx title="src/admin/widgets/product-widget.ts" highlights={queryHighlights} -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { Button, Container } from "@medusajs/ui" -import { useQuery } from "@tanstack/react-query" -import { sdk } from "../lib/config" -import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types" +To learn how envirnment variables are generally loaded in Medusa based on your application's environment, check out [this chapter](https://docs.medusajs.com/learn/fundamentals/environment-variables/index.html.md). -const ProductWidget = () => { - const { data, isLoading } = useQuery({ - queryFn: () => sdk.admin.product.list(), - queryKey: ["products"], - }) - - return ( - - {isLoading && Loading...} - {data?.products && ( -
    - {data.products.map((product) => ( -
  • {product.title}
  • - ))} -
- )} -
- ) -} +## How to Set Environment Variables -export const config = defineWidgetConfig({ - zone: "product.list.before", -}) +The Medusa Admin is built on top of [Vite](https://vite.dev/). To set an environment variable that you want to use in a widget or UI route, prefix the environment variable with `VITE_`. -export default ProductWidget +For example: + +```plain +VITE_MY_API_KEY=sk_123 ``` -### Mutation +*** -```tsx title="src/admin/widgets/product-widget.ts" highlights={mutationHighlights} -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { Button, Container } from "@medusajs/ui" -import { useMutation } from "@tanstack/react-query" -import { sdk } from "../lib/config" -import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types" +## How to Use Environment Variables -const ProductWidget = ({ - data: productData, -}: DetailWidgetProps) => { - const { mutateAsync } = useMutation({ - mutationFn: (payload: HttpTypes.AdminUpdateProduct) => - sdk.admin.product.update(productData.id, payload), - onSuccess: () => alert("updated product"), - }) +To access or use an environment variable starting with `VITE_`, use the `import.meta.env` object. - const handleUpdate = () => { - mutateAsync({ - title: "New Product Title", - }) - } - +For example: + +```tsx highlights={[["8"]]} +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Container, Heading } from "@medusajs/ui" + +const ProductWidget = () => { return ( - +
+ API Key: {import.meta.env.VITE_MY_API_KEY} +
) } @@ -5121,265 +5192,28 @@ export const config = defineWidgetConfig({ export default ProductWidget ``` -You can also send requests to custom routes as explained in the [JS SDK reference](https://docs.medusajs.com/resources/js-sdk/index.html.md). - -### Use Route Loaders for Initial Data - -You may need to retrieve data before your component is rendered, or you may need to pass some initial data to your component to be used while data is being fetched. In those cases, you can use a [route loader](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md). +In this example, you display the API key in a widget using `import.meta.env.VITE_MY_API_KEY`. -*** +### Type Error on import.meta.env -## Global Variables in Admin Customizations +If you receive a type error on `import.meta.env`, create the file `src/admin/vite-env.d.ts` with the following content: -In your admin customizations, you can use the following global variables: +```ts title="src/admin/vite-env.d.ts" +/// +``` -- `__BASE__`: The base path of the Medusa Admin, as set in the [admin.path](https://docs.medusajs.com/resources/references/medusa-config#path/index.html.md) configuration in `medusa-config.ts`. -- `__BACKEND_URL__`: The URL to the Medusa backend, as set in the [admin.backendUrl](https://docs.medusajs.com/resources/references/medusa-config#backendurl/index.html.md) configuration in `medusa-config.ts`. -- `__STOREFRONT_URL__`: The URL to the storefront, as set in the [admin.storefrontUrl](https://docs.medusajs.com/resources/references/medusa-config#storefrontUrl/index.html.md) configuration in `medusa-config.ts`. +This file tells TypeScript to recognize the `import.meta.env` object and enhances the types of your custom environment variables. *** -## Admin Translations +## Check Node Environment in Admin Customizations -The Medusa Admin dashboard can be displayed in languages other than English, which is the default. Other languages are added through community contributions. +To check the current environment, Vite exposes two variables: -Learn how to add a new language translation for the Medusa Admin in [this guide](https://docs.medusajs.com/resources/contribution-guidelines/admin-translations/index.html.md). +- `import.meta.env.DEV`: Returns `true` if the current environment is development. +- `import.meta.env.PROD`: Returns `true` if the current environment is production. - -# Admin UI Routes - -In this chapter, you’ll learn how to create a UI route in the admin dashboard. - -## What is a UI Route? - -The Medusa Admin dashboard is customizable, allowing you to add new pages, called UI routes. You create a UI route as a React component showing custom content that allow admin users to perform custom actions. - -For example, you can add a new page to show and manage product reviews, which aren't available natively in Medusa. - -*** - -## How to Create a UI Route? - -### Prerequisites - -- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md) - -You create a UI route in a `page.tsx` file under a sub-directory of `src/admin/routes` directory. The file's path relative to `src/admin/routes` determines its path in the dashboard. The file’s default export must be the UI route’s React component. - -For example, create the file `src/admin/routes/custom/page.tsx` with the following content: - -![Example of UI route file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867243/Medusa%20Book/ui-route-dir-overview_tgju25.jpg) - -```tsx title="src/admin/routes/custom/page.tsx" -import { Container, Heading } from "@medusajs/ui" - -const CustomPage = () => { - return ( - -
- This is my custom route -
-
- ) -} - -export default CustomPage -``` - -You add a new route at `http://localhost:9000/app/custom`. The `CustomPage` component holds the page's content, which currently only shows a heading. - -In the route, you use [Medusa UI](https://docs.medusajs.com/ui/index.html.md), a package that Medusa maintains to allow you to customize the dashboard with the same components used to build it. - -The UI route component must be created as an arrow function. - -### Test the UI Route - -To test the UI route, start the Medusa application: - -```bash npm2yarn -npm run dev -``` - -Then, after logging into the admin dashboard, open the page `http://localhost:9000/app/custom` to see your custom page. - -*** - -## Show UI Route in the Sidebar - -To add a sidebar item for your custom UI route, export a configuration object in the UI route's file: - -```tsx title="src/admin/routes/custom/page.tsx" highlights={highlights} -import { defineRouteConfig } from "@medusajs/admin-sdk" -import { ChatBubbleLeftRight } from "@medusajs/icons" -import { Container, Heading } from "@medusajs/ui" - -const CustomPage = () => { - return ( - -
- This is my custom route -
-
- ) -} - -export const config = defineRouteConfig({ - label: "Custom Route", - icon: ChatBubbleLeftRight, -}) - -export default CustomPage -``` - -The configuration object is created using `defineRouteConfig` from the Medusa Framework. It accepts the following properties: - -- `label`: the sidebar item’s label. -- `icon`: an optional React component used as an icon in the sidebar. - -The above example adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md). - -### Nested UI Routes - -Consider that along the UI route above at `src/admin/routes/custom/page.tsx` you create a nested UI route at `src/admin/routes/custom/nested/page.tsx` that also exports route configurations: - -![Example of nested UI route file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867243/Medusa%20Book/ui-route-dir-overview_tgju25.jpg) - -```tsx title="src/admin/routes/custom/nested/page.tsx" -import { defineRouteConfig } from "@medusajs/admin-sdk" -import { Container, Heading } from "@medusajs/ui" - -const NestedCustomPage = () => { - return ( - -
- This is my nested custom route -
-
- ) -} - -export const config = defineRouteConfig({ - label: "Nested Route", -}) - -export default NestedCustomPage -``` - -This UI route is shown in the sidebar as an item nested in the parent "Custom Route" item. Nested items are only shown when the parent sidebar items (in this case, "Custom Route") are clicked. - -#### Caveats - -Some caveats for nested UI routes in the sidebar: - -- Nested dynamic UI routes, such as one created at `src/admin/routes/custom/[id]/page.tsx` aren't added to the sidebar as it's not possible to link to a dynamic route. If the dynamic route exports route configurations, a warning is logged in the browser's console. -- Nested routes in setting pages aren't shown in the sidebar to follow the admin's design conventions. -- The `icon` configuration is ignored for the sidebar item of nested UI route to follow the admin's design conventions. - -### Route Under Existing Admin Route - -You can add a custom UI route under an existing route. For example, you can add a route under the orders route: - -```tsx title="src/admin/routes/orders/nested/page.tsx" -import { defineRouteConfig } from "@medusajs/admin-sdk" -import { Container, Heading } from "@medusajs/ui" - -const NestedOrdersPage = () => { - return ( - -
- Nested Orders Page -
-
- ) -} - -export const config = defineRouteConfig({ - label: "Nested Orders", - nested: "/orders", -}) - -export default NestedOrdersPage -``` - -The `nested` property passed to `defineRouteConfig` specifies which route this custom route is nested under. This route will now show in the sidebar under the existing "Orders" sidebar item. - -*** - -## Create Settings Page - -To create a page under the settings section of the admin dashboard, create a UI route under the path `src/admin/routes/settings`. - -For example, create a UI route at `src/admin/routes/settings/custom/page.tsx`: - -![Example of settings UI route file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867435/Medusa%20Book/setting-ui-route-dir-overview_kytbh8.jpg) - -```tsx title="src/admin/routes/settings/custom/page.tsx" -import { defineRouteConfig } from "@medusajs/admin-sdk" -import { Container, Heading } from "@medusajs/ui" - -const CustomSettingPage = () => { - return ( - -
- Custom Setting Page -
-
- ) -} - -export const config = defineRouteConfig({ - label: "Custom", -}) - -export default CustomSettingPage -``` - -This adds a page under the path `/app/settings/custom`. An item is also added to the settings sidebar with the label `Custom`. - -*** - -## Path Parameters - -A UI route can accept path parameters if the name of any of the directories in its path is of the format `[param]`. - -For example, create the file `src/admin/routes/custom/[id]/page.tsx` with the following content: - -![Example of UI route file with path parameters in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867748/Medusa%20Book/path-param-ui-route-dir-overview_kcfbev.jpg) - -```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={[["5", "", "Retrieve the path parameter."], ["10", "{id}", "Show the path parameter."]]} -import { useParams } from "react-router-dom" -import { Container, Heading } from "@medusajs/ui" - -const CustomPage = () => { - const { id } = useParams() - - return ( - -
- Passed ID: {id} -
-
- ) -} - -export default CustomPage -``` - -You access the passed parameter using `react-router-dom`'s [useParams hook](https://reactrouter.com/en/main/hooks/use-params). - -If you run the Medusa application and go to `localhost:9000/app/custom/123`, you'll see `123` printed in the page. - -*** - -## Admin Components List - -To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components. - -*** - -## More Routes Customizations - -For more customizations related to routes, refer to the [Routing Customizations chapter](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md). +Learn more about other Vite environment variables in the [Vite documentation](https://vite.dev/guide/env-and-mode). # Admin Routing Customizations @@ -5535,25 +5369,156 @@ export const handle = { Refer to [react-router-dom’s documentation](https://reactrouter.com/en/6.29.0) for components and hooks that you can use in your admin customizations. -# Admin Widgets - -In this chapter, you’ll learn more about widgets and how to use them. - -## What is an Admin Widget? +# Admin Development Tips -The Medusa Admin dashboard's pages are customizable to insert widgets of custom content in pre-defined injection zones. You create these widgets as React components that allow admin users to perform custom actions. +In this chapter, you'll find some tips for your admin development. -For example, you can add a widget on the product details page that allow admin users to sync products to a third-party service. +## Send Requests to API Routes -*** +To send a request to an API route in the Medusa Application, use Medusa's [JS SDK](https://docs.medusajs.com/resources/js-sdk/index.html.md) with [Tanstack Query](https://tanstack.com/query/latest). Both of these tools are installed in your project by default. -## How to Create a Widget? +Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency. -### Prerequisites +First, create the file `src/admin/lib/config.ts` to setup the SDK for use in your customizations: -- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md) +```ts +import Medusa from "@medusajs/js-sdk" -You create a widget in a `.tsx` file under the `src/admin/widgets` directory. The file’s default export must be the widget, which is the React component that renders the custom content. The file must also export the widget’s configurations indicating where to insert the widget. +export const sdk = new Medusa({ + baseUrl: import.meta.env.VITE_BACKEND_URL || "/", + debug: import.meta.env.DEV, + auth: { + type: "session", + }, +}) +``` + +Notice that you use `import.meta.env` to access environment variables in your customizations, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/admin/environment-variables/index.html.md). + +Learn more about the JS SDK's configurations [this documentation](https://docs.medusajs.com/resources/js-sdk#js-sdk-configurations/index.html.md). + +Then, use the configured SDK with the `useQuery` Tanstack Query hook to send `GET` requests, and `useMutation` hook to send `POST` or `DELETE` requests. + +For example: + +### Query + +```tsx title="src/admin/widgets/product-widget.ts" highlights={queryHighlights} +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Button, Container } from "@medusajs/ui" +import { useQuery } from "@tanstack/react-query" +import { sdk } from "../lib/config" +import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types" + +const ProductWidget = () => { + const { data, isLoading } = useQuery({ + queryFn: () => sdk.admin.product.list(), + queryKey: ["products"], + }) + + return ( + + {isLoading && Loading...} + {data?.products && ( +
    + {data.products.map((product) => ( +
  • {product.title}
  • + ))} +
+ )} +
+ ) +} + +export const config = defineWidgetConfig({ + zone: "product.list.before", +}) + +export default ProductWidget +``` + +### Mutation + +```tsx title="src/admin/widgets/product-widget.ts" highlights={mutationHighlights} +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Button, Container } from "@medusajs/ui" +import { useMutation } from "@tanstack/react-query" +import { sdk } from "../lib/config" +import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types" + +const ProductWidget = ({ + data: productData, +}: DetailWidgetProps) => { + const { mutateAsync } = useMutation({ + mutationFn: (payload: HttpTypes.AdminUpdateProduct) => + sdk.admin.product.update(productData.id, payload), + onSuccess: () => alert("updated product"), + }) + + const handleUpdate = () => { + mutateAsync({ + title: "New Product Title", + }) + } + + return ( + + + + ) +} + +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) + +export default ProductWidget +``` + +You can also send requests to custom routes as explained in the [JS SDK reference](https://docs.medusajs.com/resources/js-sdk/index.html.md). + +### Use Route Loaders for Initial Data + +You may need to retrieve data before your component is rendered, or you may need to pass some initial data to your component to be used while data is being fetched. In those cases, you can use a [route loader](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md). + +*** + +## Global Variables in Admin Customizations + +In your admin customizations, you can use the following global variables: + +- `__BASE__`: The base path of the Medusa Admin, as set in the [admin.path](https://docs.medusajs.com/resources/references/medusa-config#path/index.html.md) configuration in `medusa-config.ts`. +- `__BACKEND_URL__`: The URL to the Medusa backend, as set in the [admin.backendUrl](https://docs.medusajs.com/resources/references/medusa-config#backendurl/index.html.md) configuration in `medusa-config.ts`. +- `__STOREFRONT_URL__`: The URL to the storefront, as set in the [admin.storefrontUrl](https://docs.medusajs.com/resources/references/medusa-config#storefrontUrl/index.html.md) configuration in `medusa-config.ts`. + +*** + +## Admin Translations + +The Medusa Admin dashboard can be displayed in languages other than English, which is the default. Other languages are added through community contributions. + +Learn how to add a new language translation for the Medusa Admin in [this guide](https://docs.medusajs.com/resources/contribution-guidelines/admin-translations/index.html.md). + + +# Admin Widgets + +In this chapter, you’ll learn more about widgets and how to use them. + +## What is an Admin Widget? + +The Medusa Admin dashboard's pages are customizable to insert widgets of custom content in pre-defined injection zones. You create these widgets as React components that allow admin users to perform custom actions. + +For example, you can add a widget on the product details page that allow admin users to sync products to a third-party service. + +*** + +## How to Create a Widget? + +### Prerequisites + +- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md) + +You create a widget in a `.tsx` file under the `src/admin/widgets` directory. The file’s default export must be the widget, which is the React component that renders the custom content. The file must also export the widget’s configurations indicating where to insert the widget. For example, create the file `src/admin/widgets/product-widget.tsx` with the following content: @@ -5654,192 +5619,240 @@ Refer to [this reference](https://docs.medusajs.com/resources/admin-widget-injec To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components. -# Seed Data with Custom CLI Script +# Admin UI Routes -In this chapter, you'll learn how to seed data using a custom CLI script. +In this chapter, you’ll learn how to create a UI route in the admin dashboard. -## How to Seed Data +## What is a UI Route? -To seed dummy data for development or demo purposes, use a custom CLI script. +The Medusa Admin dashboard is customizable, allowing you to add new pages, called UI routes. You create a UI route as a React component showing custom content that allow admin users to perform custom actions. -In the CLI script, use your custom workflows or Medusa's existing workflows, which you can browse in [this reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md), to seed data. +For example, you can add a new page to show and manage product reviews, which aren't available natively in Medusa. -### Example: Seed Dummy Products +*** -In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products. +## How to Create a UI Route? -First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script: +### Prerequisites + +- [Medusa application installed](https://docs.medusajs.com/learn/installation/index.html.md) + +You create a UI route in a `page.tsx` file under a sub-directory of `src/admin/routes` directory. The file's path relative to `src/admin/routes` determines its path in the dashboard. The file’s default export must be the UI route’s React component. + +For example, create the file `src/admin/routes/custom/page.tsx` with the following content: + +![Example of UI route file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867243/Medusa%20Book/ui-route-dir-overview_tgju25.jpg) + +```tsx title="src/admin/routes/custom/page.tsx" +import { Container, Heading } from "@medusajs/ui" + +const CustomPage = () => { + return ( + +
+ This is my custom route +
+
+ ) +} + +export default CustomPage +``` + +You add a new route at `http://localhost:9000/app/custom`. The `CustomPage` component holds the page's content, which currently only shows a heading. + +In the route, you use [Medusa UI](https://docs.medusajs.com/ui/index.html.md), a package that Medusa maintains to allow you to customize the dashboard with the same components used to build it. + +The UI route component must be created as an arrow function. + +### Test the UI Route + +To test the UI route, start the Medusa application: ```bash npm2yarn -npm install --save-dev @faker-js/faker +npm run dev ``` -Then, create the file `src/scripts/demo-products.ts` with the following content: +Then, after logging into the admin dashboard, open the page `http://localhost:9000/app/custom` to see your custom page. -```ts title="src/scripts/demo-products.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports" -import { ExecArgs } from "@medusajs/framework/types" -import { faker } from "@faker-js/faker" -import { - ContainerRegistrationKeys, - Modules, - ProductStatus, -} from "@medusajs/framework/utils" -import { - createInventoryLevelsWorkflow, - createProductsWorkflow, -} from "@medusajs/medusa/core-flows" +*** -export default async function seedDummyProducts({ - container, -}: ExecArgs) { - const salesChannelModuleService = container.resolve( - Modules.SALES_CHANNEL - ) - const logger = container.resolve( - ContainerRegistrationKeys.LOGGER - ) - const query = container.resolve( - ContainerRegistrationKeys.QUERY - ) +## Show UI Route in the Sidebar - const defaultSalesChannel = await salesChannelModuleService - .listSalesChannels({ - name: "Default Sales Channel", - }) +To add a sidebar item for your custom UI route, export a configuration object in the UI route's file: - const sizeOptions = ["S", "M", "L", "XL"] - const colorOptions = ["Black", "White"] - const currency_code = "eur" - const productsNum = 50 +```tsx title="src/admin/routes/custom/page.tsx" highlights={highlights} +import { defineRouteConfig } from "@medusajs/admin-sdk" +import { ChatBubbleLeftRight } from "@medusajs/icons" +import { Container, Heading } from "@medusajs/ui" - // TODO seed products +const CustomPage = () => { + return ( + +
+ This is my custom route +
+
+ ) } + +export const config = defineRouteConfig({ + label: "Custom Route", + icon: ChatBubbleLeftRight, +}) + +export default CustomPage ``` -So far, in the script, you: +The configuration object is created using `defineRouteConfig` from the Medusa Framework. It accepts the following properties: -- Resolve the Sales Channel Module's main service to retrieve the application's default sales channel. This is the sales channel the dummy products will be available in. -- Resolve the Logger to log messages in the terminal, and Query to later retrieve data useful for the seeded products. -- Initialize some default data to use when seeding the products next. +- `label`: the sidebar item’s label. +- `icon`: an optional React component used as an icon in the sidebar. -Next, replace the `TODO` with the following: +The above example adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md). -```ts title="src/scripts/demo-products.ts" -const productsData = new Array(productsNum).fill(0).map((_, index) => { - const title = faker.commerce.product() + "_" + index - return { - title, - is_giftcard: true, - description: faker.commerce.productDescription(), - status: ProductStatus.PUBLISHED, - options: [ - { - title: "Size", - values: sizeOptions, - }, - { - title: "Color", - values: colorOptions, - }, - ], - images: [ - { - url: faker.image.urlPlaceholder({ - text: title, - }), - }, - { - url: faker.image.urlPlaceholder({ - text: title, - }), - }, - ], - variants: new Array(10).fill(0).map((_, variantIndex) => ({ - title: `${title} ${variantIndex}`, - sku: `variant-${variantIndex}${index}`, - prices: new Array(10).fill(0).map((_, priceIndex) => ({ - currency_code, - amount: 10 * priceIndex, - })), - options: { - Size: sizeOptions[Math.floor(Math.random() * 3)], - }, - })), - shipping_profile_id: "sp_123", - sales_channels: [ - { - id: defaultSalesChannel[0].id, - }, - ], - } +### Nested UI Routes + +Consider that along the UI route above at `src/admin/routes/custom/page.tsx` you create a nested UI route at `src/admin/routes/custom/nested/page.tsx` that also exports route configurations: + +![Example of nested UI route file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867243/Medusa%20Book/ui-route-dir-overview_tgju25.jpg) + +```tsx title="src/admin/routes/custom/nested/page.tsx" +import { defineRouteConfig } from "@medusajs/admin-sdk" +import { Container, Heading } from "@medusajs/ui" + +const NestedCustomPage = () => { + return ( + +
+ This is my nested custom route +
+
+ ) +} + +export const config = defineRouteConfig({ + label: "Nested Route", }) -// TODO seed products +export default NestedCustomPage ``` -You generate fifty products using the sales channel and variables you initialized, and using Faker for random data, such as the product's title or images. +This UI route is shown in the sidebar as an item nested in the parent "Custom Route" item. Nested items are only shown when the parent sidebar items (in this case, "Custom Route") are clicked. -Then, replace the new `TODO` with the following: +#### Caveats -```ts title="src/scripts/demo-products.ts" -const { result: products } = await createProductsWorkflow(container).run({ - input: { - products: productsData, - }, -}) +Some caveats for nested UI routes in the sidebar: -logger.info(`Seeded ${products.length} products.`) +- Nested dynamic UI routes, such as one created at `src/admin/routes/custom/[id]/page.tsx` aren't added to the sidebar as it's not possible to link to a dynamic route. If the dynamic route exports route configurations, a warning is logged in the browser's console. +- Nested routes in setting pages aren't shown in the sidebar to follow the admin's design conventions. +- The `icon` configuration is ignored for the sidebar item of nested UI route to follow the admin's design conventions. -// TODO add inventory levels -``` +### Route Under Existing Admin Route -You create the generated products using the `createProductsWorkflow` imported previously from `@medusajs/medusa/core-flows`. It accepts the product data as input, and returns the created products. +You can add a custom UI route under an existing route. For example, you can add a route under the orders route: -Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following: +```tsx title="src/admin/routes/orders/nested/page.tsx" +import { defineRouteConfig } from "@medusajs/admin-sdk" +import { Container, Heading } from "@medusajs/ui" -```ts title="src/scripts/demo-products.ts" -logger.info("Seeding inventory levels.") +const NestedOrdersPage = () => { + return ( + +
+ Nested Orders Page +
+
+ ) +} -const { data: stockLocations } = await query.graph({ - entity: "stock_location", - fields: ["id"], +export const config = defineRouteConfig({ + label: "Nested Orders", + nested: "/orders", }) -const { data: inventoryItems } = await query.graph({ - entity: "inventory_item", - fields: ["id"], -}) +export default NestedOrdersPage +``` -const inventoryLevels = inventoryItems.map((inventoryItem) => ({ - location_id: stockLocations[0].id, - stocked_quantity: 1000000, - inventory_item_id: inventoryItem.id, -})) +The `nested` property passed to `defineRouteConfig` specifies which route this custom route is nested under. This route will now show in the sidebar under the existing "Orders" sidebar item. -await createInventoryLevelsWorkflow(container).run({ - input: { - inventory_levels: inventoryLevels, - }, +*** + +## Create Settings Page + +To create a page under the settings section of the admin dashboard, create a UI route under the path `src/admin/routes/settings`. + +For example, create a UI route at `src/admin/routes/settings/custom/page.tsx`: + +![Example of settings UI route file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867435/Medusa%20Book/setting-ui-route-dir-overview_kytbh8.jpg) + +```tsx title="src/admin/routes/settings/custom/page.tsx" +import { defineRouteConfig } from "@medusajs/admin-sdk" +import { Container, Heading } from "@medusajs/ui" + +const CustomSettingPage = () => { + return ( + +
+ Custom Setting Page +
+
+ ) +} + +export const config = defineRouteConfig({ + label: "Custom", }) -logger.info("Finished seeding inventory levels data.") +export default CustomSettingPage ``` -You use Query to retrieve the stock location, to use the first location in the application, and the inventory items. +This adds a page under the path `/app/settings/custom`. An item is also added to the settings sidebar with the label `Custom`. -Then, you generate inventory levels for each inventory item, associating it with the first stock location. +*** -Finally, you use the `createInventoryLevelsWorkflow` from Medusa's core workflows to create the inventory levels. +## Path Parameters -### Test Script +A UI route can accept path parameters if the name of any of the directories in its path is of the format `[param]`. -To test out the script, run the following command in your project's directory: +For example, create the file `src/admin/routes/custom/[id]/page.tsx` with the following content: -```bash -npx medusa exec ./src/scripts/demo-products.ts +![Example of UI route file with path parameters in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732867748/Medusa%20Book/path-param-ui-route-dir-overview_kcfbev.jpg) + +```tsx title="src/admin/routes/custom/[id]/page.tsx" highlights={[["5", "", "Retrieve the path parameter."], ["10", "{id}", "Show the path parameter."]]} +import { useParams } from "react-router-dom" +import { Container, Heading } from "@medusajs/ui" + +const CustomPage = () => { + const { id } = useParams() + + return ( + +
+ Passed ID: {id} +
+
+ ) +} + +export default CustomPage ``` -This seeds the products to your database. If you run your Medusa application and view the products in the dashboard, you'll find fifty new products. +You access the passed parameter using `react-router-dom`'s [useParams hook](https://reactrouter.com/en/main/hooks/use-params). + +If you run the Medusa application and go to `localhost:9000/app/custom/123`, you'll see `123` printed in the page. + +*** + +## Admin Components List + +To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](https://docs.medusajs.com/resources/admin-components/index.html.md) to find common components. + +*** + +## More Routes Customizations + +For more customizations related to routes, refer to the [Routing Customizations chapter](https://docs.medusajs.com/learn/fundamentals/admin/routing/index.html.md). # Pass Additional Data to Medusa's API Route @@ -7337,217 +7350,76 @@ For example, if you omit the `a` parameter, you'll receive a `400` response code To see different examples and learn more about creating a validation schema, refer to [Zod's documentation](https://zod.dev). -# Event Data Payload - -In this chapter, you'll learn how subscribers receive an event's data payload. +# Add Data Model Check Constraints -## Access Event's Data Payload +In this chapter, you'll learn how to add check constraints to your data model. -When events are emitted, they’re emitted with a data payload. +## What is a Check Constraint? -The object that the subscriber function receives as a parameter has an `event` property, which is an object holding the event payload in a `data` property with additional context. +A check constraint is a condition that must be satisfied by records inserted into a database table, otherwise an error is thrown. -For example: +For example, if you have a data model with a `price` property, you want to only allow positive number values. So, you add a check constraint that fails when inserting a record with a negative price value. -```ts title="src/subscribers/product-created.ts" highlights={highlights} collapsibleLines="1-5" expandButtonLabel="Show Imports" -import type { - SubscriberArgs, - SubscriberConfig, -} from "@medusajs/framework" +*** -export default async function productCreateHandler({ - event, -}: SubscriberArgs<{ id: string }>) { - const productId = event.data.id - console.log(`The product ${productId} was created`) -} +## How to Set a Check Constraint? -export const config: SubscriberConfig = { - event: "product.created", -} -``` +To set check constraints on a data model, use the `checks` method. This method accepts an array of check constraints to apply on the data model. -The `event` object has the following properties: +For example, to set a check constraint on a `price` property that ensures its value can only be a positive number: -- data: (\`object\`) The data payload of the event. Its properties are different for each event. -- name: (string) The name of the triggered event. -- metadata: (\`object\`) Additional data and context of the emitted event. +```ts highlights={checks1Highlights} +import { model } from "@medusajs/framework/utils" -This logs the product ID received in the `product.created` event’s data payload to the console. +const CustomProduct = model.define("custom_product", { + // ... + price: model.bigNumber(), +}) +.checks([ + (columns) => `${columns.price} >= 0`, +]) +``` -{/* --- +The item passed in the array parameter of `checks` can be a callback function that accepts as a parameter an object whose keys are the names of the properties in the data model schema, and values the respective column name in the database. -## List of Events with Data Payload +The function returns a string indicating the [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). In the expression, use the `columns` parameter to access a property's column name. -Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads. */} +You can also pass an object to the `checks` method: +```ts highlights={checks2Highlights} +import { model } from "@medusajs/framework/utils" -# Emit Workflow and Service Events - -In this chapter, you'll learn about event types and how to emit an event in a service or workflow. - -## Event Types - -In your customization, you can emit an event, then listen to it in a subscriber and perform an asynchronus action, such as send a notification or data to a third-party system. - -There are two types of events in Medusa: - -1. Workflow event: an event that's emitted in a workflow after a commerce feature is performed. For example, Medusa emits the `order.placed` event after a cart is completed. -2. Service event: an event that's emitted to track, trace, or debug processes under the hood. For example, you can emit an event with an audit trail. - -### Which Event Type to Use? - -**Workflow events** are the most common event type in development, as most custom features and customizations are built around workflows. - -Some examples of workflow events: - -1. When a user creates a blog post and you're emitting an event to send a newsletter email. -2. When you finish syncing products to a third-party system and you want to notify the admin user of new products added. -3. When a customer purchases a digital product and you want to generate and send it to them. - -You should only go for a **service event** if you're emitting an event for processes under the hood that don't directly affect front-facing features. - -Some examples of service events: - -1. When you're tracing data manipulation and changes, and you want to track every time some custom data is changed. -2. When you're syncing data with a search engine. - -*** - -## Emit Event in a Workflow - -To emit a workflow event, use the `emitEventStep` helper step provided in the `@medusajs/medusa/core-flows` package. - -For example: - -```ts highlights={highlights} -import { - createWorkflow, -} from "@medusajs/framework/workflows-sdk" -import { - emitEventStep, -} from "@medusajs/medusa/core-flows" - -const helloWorldWorkflow = createWorkflow( - "hello-world", - () => { - // ... - - emitEventStep({ - eventName: "custom.created", - data: { - id: "123", - // other data payload - }, - }) - } -) +const CustomProduct = model.define("custom_product", { + // ... + price: model.bigNumber(), +}) +.checks([ + { + name: "custom_product_price_check", + expression: (columns) => `${columns.price} >= 0`, + }, +]) ``` -The `emitEventStep` accepts an object having the following properties: - -- `eventName`: The event's name. -- `data`: The data payload as an object. You can pass any properties in the object, and subscribers listening to the event will receive this data in the event's payload. - -In this example, you emit the event `custom.created` and pass in the data payload an ID property. - -### Test it Out - -If you execute the workflow, the event is emitted and you can see it in your application's logs. +The object accepts the following properties: -Any subscribers listening to the event are executed. +- `name`: The check constraint's name. +- `expression`: A function similar to the one that can be passed to the array. It accepts an object of columns and returns an [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). *** -## Emit Event in a Service - -To emit a service event: - -1. Resolve `event_bus` from the module's container in your service's constructor: - -### Extending Service Factory - -```ts title="src/modules/hello/service.ts" highlights={["9"]} -import { IEventBusService } from "@medusajs/framework/types" -import { MedusaService } from "@medusajs/framework/utils" - -class HelloModuleService extends MedusaService({ - MyCustom, -}){ - protected eventBusService_: AbstractEventBusModuleService - - constructor({ event_bus }) { - super(...arguments) - this.eventBusService_ = event_bus - } -} -``` - -### Without Service Factory - -```ts title="src/modules/hello/service.ts" highlights={["6"]} -import { IEventBusService } from "@medusajs/framework/types" - -class HelloModuleService { - protected eventBusService_: AbstractEventBusModuleService - - constructor({ event_bus }) { - this.eventBusService_ = event_bus - } -} -``` - -2. Use the event bus service's `emit` method in the service's methods to emit an event: - -```ts title="src/modules/hello/service.ts" highlights={serviceHighlights} -class HelloModuleService { - // ... - performAction() { - // TODO perform action - - this.eventBusService_.emit({ - name: "custom.event", - data: { - id: "123", - // other data payload - }, - }) - } -} -``` - -The method accepts an object having the following properties: - -- `name`: The event's name. -- `data`: The data payload as an object. You can pass any properties in the object, and subscribers listening to the event will receive this data in the event's payload. +## Apply in Migrations -3. By default, the Event Module's service isn't injected into your module's container. To add it to the container, pass it in the module's registration object in `medusa-config.ts` in the `dependencies` property: +After adding the check constraint, make sure to generate and run migrations if you already have the table in the database. Otherwise, the check constraint won't be reflected. -```ts title="medusa-config.ts" highlights={depsHighlight} -import { Modules } from "@medusajs/framework/utils" +To generate a migration for the data model's module then reflect it on the database, run the following command: -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "./src/modules/hello", - dependencies: [ - Modules.EVENT_BUS, - ], - }, - ], -}) +```bash +npx medusa db:generate custom_module +npx medusa db:migrate ``` -The `dependencies` property accepts an array of module registration keys. The specified modules' main services are injected into the module's container. - -That's how you can resolve it in your module's main service's constructor. - -### Test it Out - -If you execute the `performAction` method of your service, the event is emitted and you can see it in your application's logs. - -Any subscribers listening to the event are also executed. +The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database. # Configure Data Model Properties @@ -7619,383 +7491,257 @@ export default User In this example, multiple users can’t have the same email. -# Data Model Default Properties - -In this chapter, you'll learn about the properties available by default in your data model. +# Seed Data with Custom CLI Script -When you create a data model, the following properties are created for you by Medusa: +In this chapter, you'll learn how to seed data using a custom CLI script. -- `created_at`: A `dateTime` property that stores when a record of the data model was created. -- `updated_at`: A `dateTime` property that stores when a record of the data model was updated. -- `deleted_at`: A `dateTime` property that stores when a record of the data model was deleted. When you soft-delete a record, Medusa sets the `deleted_at` property to the current date. +## How to Seed Data +To seed dummy data for development or demo purposes, use a custom CLI script. -# Add Data Model Check Constraints +In the CLI script, use your custom workflows or Medusa's existing workflows, which you can browse in [this reference](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md), to seed data. -In this chapter, you'll learn how to add check constraints to your data model. +### Example: Seed Dummy Products -## What is a Check Constraint? +In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products. -A check constraint is a condition that must be satisfied by records inserted into a database table, otherwise an error is thrown. +First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script: -For example, if you have a data model with a `price` property, you want to only allow positive number values. So, you add a check constraint that fails when inserting a record with a negative price value. +```bash npm2yarn +npm install --save-dev @faker-js/faker +``` -*** +Then, create the file `src/scripts/demo-products.ts` with the following content: -## How to Set a Check Constraint? +```ts title="src/scripts/demo-products.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports" +import { ExecArgs } from "@medusajs/framework/types" +import { faker } from "@faker-js/faker" +import { + ContainerRegistrationKeys, + Modules, + ProductStatus, +} from "@medusajs/framework/utils" +import { + createInventoryLevelsWorkflow, + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" -To set check constraints on a data model, use the `checks` method. This method accepts an array of check constraints to apply on the data model. +export default async function seedDummyProducts({ + container, +}: ExecArgs) { + const salesChannelModuleService = container.resolve( + Modules.SALES_CHANNEL + ) + const logger = container.resolve( + ContainerRegistrationKeys.LOGGER + ) + const query = container.resolve( + ContainerRegistrationKeys.QUERY + ) -For example, to set a check constraint on a `price` property that ensures its value can only be a positive number: + const defaultSalesChannel = await salesChannelModuleService + .listSalesChannels({ + name: "Default Sales Channel", + }) -```ts highlights={checks1Highlights} -import { model } from "@medusajs/framework/utils" + const sizeOptions = ["S", "M", "L", "XL"] + const colorOptions = ["Black", "White"] + const currency_code = "eur" + const productsNum = 50 -const CustomProduct = model.define("custom_product", { - // ... - price: model.bigNumber(), -}) -.checks([ - (columns) => `${columns.price} >= 0`, -]) + // TODO seed products +} ``` -The item passed in the array parameter of `checks` can be a callback function that accepts as a parameter an object whose keys are the names of the properties in the data model schema, and values the respective column name in the database. - -The function returns a string indicating the [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). In the expression, use the `columns` parameter to access a property's column name. +So far, in the script, you: -You can also pass an object to the `checks` method: +- Resolve the Sales Channel Module's main service to retrieve the application's default sales channel. This is the sales channel the dummy products will be available in. +- Resolve the Logger to log messages in the terminal, and Query to later retrieve data useful for the seeded products. +- Initialize some default data to use when seeding the products next. -```ts highlights={checks2Highlights} -import { model } from "@medusajs/framework/utils" +Next, replace the `TODO` with the following: -const CustomProduct = model.define("custom_product", { - // ... - price: model.bigNumber(), +```ts title="src/scripts/demo-products.ts" +const productsData = new Array(productsNum).fill(0).map((_, index) => { + const title = faker.commerce.product() + "_" + index + return { + title, + is_giftcard: true, + description: faker.commerce.productDescription(), + status: ProductStatus.PUBLISHED, + options: [ + { + title: "Size", + values: sizeOptions, + }, + { + title: "Color", + values: colorOptions, + }, + ], + images: [ + { + url: faker.image.urlPlaceholder({ + text: title, + }), + }, + { + url: faker.image.urlPlaceholder({ + text: title, + }), + }, + ], + variants: new Array(10).fill(0).map((_, variantIndex) => ({ + title: `${title} ${variantIndex}`, + sku: `variant-${variantIndex}${index}`, + prices: new Array(10).fill(0).map((_, priceIndex) => ({ + currency_code, + amount: 10 * priceIndex, + })), + options: { + Size: sizeOptions[Math.floor(Math.random() * 3)], + }, + })), + shipping_profile_id: "sp_123", + sales_channels: [ + { + id: defaultSalesChannel[0].id, + }, + ], + } }) -.checks([ - { - name: "custom_product_price_check", - expression: (columns) => `${columns.price} >= 0`, - }, -]) -``` - -The object accepts the following properties: -- `name`: The check constraint's name. -- `expression`: A function similar to the one that can be passed to the array. It accepts an object of columns and returns an [SQL check constraint expression](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS). +// TODO seed products +``` -*** +You generate fifty products using the sales channel and variables you initialized, and using Faker for random data, such as the product's title or images. -## Apply in Migrations +Then, replace the new `TODO` with the following: -After adding the check constraint, make sure to generate and run migrations if you already have the table in the database. Otherwise, the check constraint won't be reflected. +```ts title="src/scripts/demo-products.ts" +const { result: products } = await createProductsWorkflow(container).run({ + input: { + products: productsData, + }, +}) -To generate a migration for the data model's module then reflect it on the database, run the following command: +logger.info(`Seeded ${products.length} products.`) -```bash -npx medusa db:generate custom_module -npx medusa db:migrate +// TODO add inventory levels ``` -The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database. - - -# Manage Relationships +You create the generated products using the `createProductsWorkflow` imported previously from `@medusajs/medusa/core-flows`. It accepts the product data as input, and returns the created products. -In this chapter, you'll learn how to manage relationships between data models when creating, updating, or retrieving records using the module's main service. +Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following: -## Manage One-to-One Relationship +```ts title="src/scripts/demo-products.ts" +logger.info("Seeding inventory levels.") -### BelongsTo Side of One-to-One +const { data: stockLocations } = await query.graph({ + entity: "stock_location", + fields: ["id"], +}) -When you create a record of a data model that belongs to another through a one-to-one relation, pass the ID of the other data model's record in the `{relation}_id` property, where `{relation}` is the name of the relation property. +const { data: inventoryItems } = await query.graph({ + entity: "inventory_item", + fields: ["id"], +}) -For example, assuming you have the [User and Email data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-one-relationship/index.html.md), set an email's user ID as follows: +const inventoryLevels = inventoryItems.map((inventoryItem) => ({ + location_id: stockLocations[0].id, + stocked_quantity: 1000000, + inventory_item_id: inventoryItem.id, +})) -```ts highlights={belongsHighlights} -// when creating an email -const email = await helloModuleService.createEmails({ - // other properties... - user_id: "123", +await createInventoryLevelsWorkflow(container).run({ + input: { + inventory_levels: inventoryLevels, + }, }) -// when updating an email -const email = await helloModuleService.updateEmails({ - id: "321", - // other properties... - user_id: "123", -}) +logger.info("Finished seeding inventory levels data.") ``` -In the example above, you pass the `user_id` property when creating or updating an email to specify the user it belongs to. +You use Query to retrieve the stock location, to use the first location in the application, and the inventory items. -### HasOne Side +Then, you generate inventory levels for each inventory item, associating it with the first stock location. -When you create a record of a data model that has one of another, pass the ID of the other data model's record in the relation property. +Finally, you use the `createInventoryLevelsWorkflow` from Medusa's core workflows to create the inventory levels. -For example, assuming you have the [User and Email data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-one-relationship/index.html.md), set a user's email ID as follows: +### Test Script -```ts highlights={hasOneHighlights} -// when creating a user -const user = await helloModuleService.createUsers({ - // other properties... - email: "123", -}) +To test out the script, run the following command in your project's directory: -// when updating a user -const user = await helloModuleService.updateUsers({ - id: "321", - // other properties... - email: "123", -}) +```bash +npx medusa exec ./src/scripts/demo-products.ts ``` -In the example above, you pass the `email` property when creating or updating a user to specify the email it has. - -*** - -## Manage One-to-Many Relationship +This seeds the products to your database. If you run your Medusa application and view the products in the dashboard, you'll find fifty new products. -In a one-to-many relationship, you can only manage the associations from the `belongsTo` side. -When you create a record of the data model on the `belongsTo` side, pass the ID of the other data model's record in the `{relation}_id` property, where `{relation}` is the name of the relation property. +# Data Model Default Properties -For example, assuming you have the [Product and Store data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-many-relationship/index.html.md), set a product's store ID as follows: +In this chapter, you'll learn about the properties available by default in your data model. -```ts highlights={manyBelongsHighlights} -// when creating a product -const product = await helloModuleService.createProducts({ - // other properties... - store_id: "123", -}) +When you create a data model, the following properties are created for you by Medusa: -// when updating a product -const product = await helloModuleService.updateProducts({ - id: "321", - // other properties... - store_id: "123", -}) -``` +- `created_at`: A `dateTime` property that stores when a record of the data model was created. +- `updated_at`: A `dateTime` property that stores when a record of the data model was updated. +- `deleted_at`: A `dateTime` property that stores when a record of the data model was deleted. When you soft-delete a record, Medusa sets the `deleted_at` property to the current date. -In the example above, you pass the `store_id` property when creating or updating a product to specify the store it belongs to. -*** +# Infer Type of Data Model -## Manage Many-to-Many Relationship +In this chapter, you'll learn how to infer the type of a data model. -If your many-to-many relation is represented with a [pivotEntity](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), refer to [this section](#manage-many-to-many-relationship-with-pivotentity) instead. +## How to Infer Type of Data Model? -### Create Associations +Consider you have a `MyCustom` data model. You can't reference this data model in a type, such as a workflow input or service method output types, since it's a variable. -When you create a record of a data model that has a many-to-many relationship to another data model, pass an array of IDs of the other data model's records in the relation property. +Instead, Medusa provides `InferTypeOf` that transforms your data model to a type. -For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), set the association between products and orders as follows: +For example: -```ts highlights={manyHighlights} -// when creating a product -const product = await helloModuleService.createProducts({ - // other properties... - orders: ["123", "321"], -}) +```ts +import { InferTypeOf } from "@medusajs/framework/types" +import { MyCustom } from "../models/my-custom" // relative path to the model -// when creating an order -const order = await helloModuleService.createOrders({ - id: "321", - // other properties... - products: ["123", "321"], -}) +export type MyCustom = InferTypeOf ``` -In the example above, you pass the `orders` property when you create a product, and you pass the `products` property when you create an order. +`InferTypeOf` accepts as a type argument the type of the data model. -### Update Associations +Since the `MyCustom` data model is a variable, use the `typeof` operator to pass the data model as a type argument to `InferTypeOf`. -When you use the `update` methods generated by the service factory, you also pass an array of IDs as the relation property's value to add new associated records. +You can now use the `MyCustom` type to reference a data model in other types, such as in workflow inputs or service method outputs: -However, this removes any existing associations to records whose IDs aren't included in the array. +```ts title="Example Service" +// other imports... +import { InferTypeOf } from "@medusajs/framework/types" +import { MyCustom } from "../models/my-custom" -For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), you update the product's related orders as so: +type MyCustom = InferTypeOf -```ts -const product = await helloModuleService.updateProducts({ - id: "123", - // other properties... - orders: ["321"], -}) +class HelloModuleService extends MedusaService({ MyCustom }) { + async doSomething(): Promise { + // ... + } +} ``` -If the product was associated with an order, and you don't include that order's ID in the `orders` array, the association between the product and order is removed. -So, to add a new association without removing existing ones, retrieve the product first to pass its associated orders when updating the product: +# Data Model Database Index -```ts highlights={updateAssociationHighlights} -const product = await helloModuleService.retrieveProduct( - "123", - { - relations: ["orders"], - } -) +In this chapter, you’ll learn how to define a database index on a data model. -const updatedProduct = await helloModuleService.updateProducts({ - id: product.id, - // other properties... - orders: [ - ...product.orders.map((order) => order.id), - "321", - ], -}) -``` +## Define Database Index on Property -This keeps existing associations between the product and orders, and adds a new one. +Use the `index` method on a property's definition to define a database index. -*** +For example: -## Manage Many-to-Many Relationship with pivotEntity - -If your many-to-many relation is represented without a [pivotEntity](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), refer to [this section](#manage-many-to-many-relationship) instead. - -If you have a many-to-many relation with a `pivotEntity` specified, make sure to pass the data model representing the pivot table to [MedusaService](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that your module's service extends. - -For example, assuming you have the [Order, Product, and OrderProduct models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), add `OrderProduct` to `MedusaService`'s object parameter: - -```ts highlights={["4"]} -class HelloModuleService extends MedusaService({ - Order, - Product, - OrderProduct, -}) {} -``` - -This will generate Create, Read, Update and Delete (CRUD) methods for the `OrderProduct` data model, which you can use to create relations between orders and products and manage the extra columns in the pivot table. - -For example: - -```ts -// create order-product association -const orderProduct = await helloModuleService.createOrderProducts({ - order_id: "123", - product_id: "123", - metadata: { - test: true, - }, -}) - -// update order-product association -const orderProduct = await helloModuleService.updateOrderProducts({ - id: "123", - metadata: { - test: false, - }, -}) - -// delete order-product association -await helloModuleService.deleteOrderProducts("123") -``` - -Since the `OrderProduct` data model belongs to the `Order` and `Product` data models, you can set its order and product as explained in the [one-to-many relationship section](#manage-one-to-many-relationship) using `order_id` and `product_id`. - -Refer to the [service factory reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) for a full list of generated methods and their usages. - -*** - -## Retrieve Records of Relation - -The `list`, `listAndCount`, and `retrieve` methods of a module's main service accept as a second parameter an object of options. - -To retrieve the records associated with a data model's records through a relationship, pass in the second parameter object a `relations` property whose value is an array of relationship names. - -For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), you retrieve a product's orders as follows: - -```ts highlights={retrieveHighlights} -const product = await helloModuleService.retrieveProducts( - "123", - { - relations: ["orders"], - } -) -``` - -In the example above, the retrieved product has an `orders` property, whose value is an array of orders associated with the product. - - -# Infer Type of Data Model - -In this chapter, you'll learn how to infer the type of a data model. - -## How to Infer Type of Data Model? - -Consider you have a `MyCustom` data model. You can't reference this data model in a type, such as a workflow input or service method output types, since it's a variable. - -Instead, Medusa provides `InferTypeOf` that transforms your data model to a type. - -For example: - -```ts -import { InferTypeOf } from "@medusajs/framework/types" -import { MyCustom } from "../models/my-custom" // relative path to the model - -export type MyCustom = InferTypeOf -``` - -`InferTypeOf` accepts as a type argument the type of the data model. - -Since the `MyCustom` data model is a variable, use the `typeof` operator to pass the data model as a type argument to `InferTypeOf`. - -You can now use the `MyCustom` type to reference a data model in other types, such as in workflow inputs or service method outputs: - -```ts title="Example Service" -// other imports... -import { InferTypeOf } from "@medusajs/framework/types" -import { MyCustom } from "../models/my-custom" - -type MyCustom = InferTypeOf - -class HelloModuleService extends MedusaService({ MyCustom }) { - async doSomething(): Promise { - // ... - } -} -``` - - -# Data Model’s Primary Key - -In this chapter, you’ll learn how to configure the primary key of a data model. - -## primaryKey Method - -To set any `id`, `text`, or `number` property as a primary key, use the `primaryKey` method. - -For example: - -```ts highlights={highlights} -import { model } from "@medusajs/framework/utils" - -const MyCustom = model.define("my_custom", { - id: model.id().primaryKey(), - // ... -}) - -export default MyCustom -``` - -In the example above, the `id` property is defined as the data model's primary key. - - -# Data Model Database Index - -In this chapter, you’ll learn how to define a database index on a data model. - -## Define Database Index on Property - -Use the `index` method on a property's definition to define a database index. - -For example: - -```ts highlights={highlights} -import { model } from "@medusajs/framework/utils" +```ts highlights={highlights} +import { model } from "@medusajs/framework/utils" const MyCustom = model.define("my_custom", { id: model.id().primaryKey(), @@ -8119,91 +7865,333 @@ export default MyCustom This creates a unique composite index on the `name` and `age` properties. -# Data Model Property Types +# Manage Relationships -In this chapter, you’ll learn about the types of properties in a data model’s schema. +In this chapter, you'll learn how to manage relationships between data models when creating, updating, or retrieving records using the module's main service. -## id +## Manage One-to-One Relationship -The `id` method defines an automatically generated string ID property. The generated ID is a unique string that has a mix of letters and numbers. +### BelongsTo Side of One-to-One -For example: +When you create a record of a data model that belongs to another through a one-to-one relation, pass the ID of the other data model's record in the `{relation}_id` property, where `{relation}` is the name of the relation property. -```ts highlights={idHighlights} -import { model } from "@medusajs/framework/utils" +For example, assuming you have the [User and Email data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-one-relationship/index.html.md), set an email's user ID as follows: -const MyCustom = model.define("my_custom", { - id: model.id(), - // ... +```ts highlights={belongsHighlights} +// when creating an email +const email = await helloModuleService.createEmails({ + // other properties... + user_id: "123", }) -export default MyCustom +// when updating an email +const email = await helloModuleService.updateEmails({ + id: "321", + // other properties... + user_id: "123", +}) ``` -*** - -## text +In the example above, you pass the `user_id` property when creating or updating an email to specify the user it belongs to. -The `text` method defines a string property. +### HasOne Side -For example: +When you create a record of a data model that has one of another, pass the ID of the other data model's record in the relation property. -```ts highlights={textHighlights} -import { model } from "@medusajs/framework/utils" +For example, assuming you have the [User and Email data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-one-relationship/index.html.md), set a user's email ID as follows: -const MyCustom = model.define("my_custom", { - name: model.text(), - // ... +```ts highlights={hasOneHighlights} +// when creating a user +const user = await helloModuleService.createUsers({ + // other properties... + email: "123", }) -export default MyCustom +// when updating a user +const user = await helloModuleService.updateUsers({ + id: "321", + // other properties... + email: "123", +}) ``` +In the example above, you pass the `email` property when creating or updating a user to specify the email it has. + *** -## number +## Manage One-to-Many Relationship -The `number` method defines a number property. +In a one-to-many relationship, you can only manage the associations from the `belongsTo` side. -For example: +When you create a record of the data model on the `belongsTo` side, pass the ID of the other data model's record in the `{relation}_id` property, where `{relation}` is the name of the relation property. -```ts highlights={numberHighlights} -import { model } from "@medusajs/framework/utils" +For example, assuming you have the [Product and Store data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#one-to-many-relationship/index.html.md), set a product's store ID as follows: -const MyCustom = model.define("my_custom", { - age: model.number(), - // ... +```ts highlights={manyBelongsHighlights} +// when creating a product +const product = await helloModuleService.createProducts({ + // other properties... + store_id: "123", }) -export default MyCustom +// when updating a product +const product = await helloModuleService.updateProducts({ + id: "321", + // other properties... + store_id: "123", +}) ``` -*** +In the example above, you pass the `store_id` property when creating or updating a product to specify the store it belongs to. -## float +*** -This property is only available after [Medusa v2.1.2](https://github.com/medusajs/medusa/releases/tag/v2.1.2). +## Manage Many-to-Many Relationship -The `float` method defines a number property that allows for values with decimal places. +If your many-to-many relation is represented with a [pivotEntity](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), refer to [this section](#manage-many-to-many-relationship-with-pivotentity) instead. -Use this property type when it's less important to have high precision for numbers with large decimal places. Alternatively, for higher percision, use the [bigNumber property](#bignumber). +### Create Associations -For example: +When you create a record of a data model that has a many-to-many relationship to another data model, pass an array of IDs of the other data model's records in the relation property. -```ts highlights={floatHighlights} -import { model } from "@medusajs/framework/utils" +For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), set the association between products and orders as follows: -const MyCustom = model.define("my_custom", { - rating: model.float(), - // ... +```ts highlights={manyHighlights} +// when creating a product +const product = await helloModuleService.createProducts({ + // other properties... + orders: ["123", "321"], }) -export default MyCustom +// when creating an order +const order = await helloModuleService.createOrders({ + id: "321", + // other properties... + products: ["123", "321"], +}) ``` -*** +In the example above, you pass the `orders` property when you create a product, and you pass the `products` property when you create an order. -## bigNumber +### Update Associations + +When you use the `update` methods generated by the service factory, you also pass an array of IDs as the relation property's value to add new associated records. + +However, this removes any existing associations to records whose IDs aren't included in the array. + +For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), you update the product's related orders as so: + +```ts +const product = await helloModuleService.updateProducts({ + id: "123", + // other properties... + orders: ["321"], +}) +``` + +If the product was associated with an order, and you don't include that order's ID in the `orders` array, the association between the product and order is removed. + +So, to add a new association without removing existing ones, retrieve the product first to pass its associated orders when updating the product: + +```ts highlights={updateAssociationHighlights} +const product = await helloModuleService.retrieveProduct( + "123", + { + relations: ["orders"], + } +) + +const updatedProduct = await helloModuleService.updateProducts({ + id: product.id, + // other properties... + orders: [ + ...product.orders.map((order) => order.id), + "321", + ], +}) +``` + +This keeps existing associations between the product and orders, and adds a new one. + +*** + +## Manage Many-to-Many Relationship with pivotEntity + +If your many-to-many relation is represented without a [pivotEntity](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), refer to [this section](#manage-many-to-many-relationship) instead. + +If you have a many-to-many relation with a `pivotEntity` specified, make sure to pass the data model representing the pivot table to [MedusaService](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that your module's service extends. + +For example, assuming you have the [Order, Product, and OrderProduct models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-with-custom-columns/index.html.md), add `OrderProduct` to `MedusaService`'s object parameter: + +```ts highlights={["4"]} +class HelloModuleService extends MedusaService({ + Order, + Product, + OrderProduct, +}) {} +``` + +This will generate Create, Read, Update and Delete (CRUD) methods for the `OrderProduct` data model, which you can use to create relations between orders and products and manage the extra columns in the pivot table. + +For example: + +```ts +// create order-product association +const orderProduct = await helloModuleService.createOrderProducts({ + order_id: "123", + product_id: "123", + metadata: { + test: true, + }, +}) + +// update order-product association +const orderProduct = await helloModuleService.updateOrderProducts({ + id: "123", + metadata: { + test: false, + }, +}) + +// delete order-product association +await helloModuleService.deleteOrderProducts("123") +``` + +Since the `OrderProduct` data model belongs to the `Order` and `Product` data models, you can set its order and product as explained in the [one-to-many relationship section](#manage-one-to-many-relationship) using `order_id` and `product_id`. + +Refer to the [service factory reference](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) for a full list of generated methods and their usages. + +*** + +## Retrieve Records of Relation + +The `list`, `listAndCount`, and `retrieve` methods of a module's main service accept as a second parameter an object of options. + +To retrieve the records associated with a data model's records through a relationship, pass in the second parameter object a `relations` property whose value is an array of relationship names. + +For example, assuming you have the [Order and Product data models from the previous chapter](https://docs.medusajs.com/learn/fundamentals/data-models/relationships#many-to-many-relationship/index.html.md), you retrieve a product's orders as follows: + +```ts highlights={retrieveHighlights} +const product = await helloModuleService.retrieveProducts( + "123", + { + relations: ["orders"], + } +) +``` + +In the example above, the retrieved product has an `orders` property, whose value is an array of orders associated with the product. + + +# Data Model’s Primary Key + +In this chapter, you’ll learn how to configure the primary key of a data model. + +## primaryKey Method + +To set any `id`, `text`, or `number` property as a primary key, use the `primaryKey` method. + +For example: + +```ts highlights={highlights} +import { model } from "@medusajs/framework/utils" + +const MyCustom = model.define("my_custom", { + id: model.id().primaryKey(), + // ... +}) + +export default MyCustom +``` + +In the example above, the `id` property is defined as the data model's primary key. + + +# Data Model Property Types + +In this chapter, you’ll learn about the types of properties in a data model’s schema. + +## id + +The `id` method defines an automatically generated string ID property. The generated ID is a unique string that has a mix of letters and numbers. + +For example: + +```ts highlights={idHighlights} +import { model } from "@medusajs/framework/utils" + +const MyCustom = model.define("my_custom", { + id: model.id(), + // ... +}) + +export default MyCustom +``` + +*** + +## text + +The `text` method defines a string property. + +For example: + +```ts highlights={textHighlights} +import { model } from "@medusajs/framework/utils" + +const MyCustom = model.define("my_custom", { + name: model.text(), + // ... +}) + +export default MyCustom +``` + +*** + +## number + +The `number` method defines a number property. + +For example: + +```ts highlights={numberHighlights} +import { model } from "@medusajs/framework/utils" + +const MyCustom = model.define("my_custom", { + age: model.number(), + // ... +}) + +export default MyCustom +``` + +*** + +## float + +This property is only available after [Medusa v2.1.2](https://github.com/medusajs/medusa/releases/tag/v2.1.2). + +The `float` method defines a number property that allows for values with decimal places. + +Use this property type when it's less important to have high precision for numbers with large decimal places. Alternatively, for higher percision, use the [bigNumber property](#bignumber). + +For example: + +```ts highlights={floatHighlights} +import { model } from "@medusajs/framework/utils" + +const MyCustom = model.define("my_custom", { + rating: model.float(), + // ... +}) + +export default MyCustom +``` + +*** + +## bigNumber The `bigNumber` method defines a number property that expects large numbers, such as prices. @@ -8326,91 +8314,47 @@ export default MyCustom Refer to the [Data Model API reference](https://docs.medusajs.com/resources/references/data-model/index.html.md) for a full reference of the properties. -# Searchable Data Model Property +# Data Model Relationships -In this chapter, you'll learn what a searchable property is and how to define it. +In this chapter, you’ll learn how to define relationships between data models in your module. -## What is a Searchable Property? +## What is a Relationship Property? -Methods generated by the [service factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters. +A relationship property defines an association in the database between two models. It's created using the Data Model Language (DML) methods, such as `hasOne` or `belongsTo`. -When the `q` filter is passed, the data model's searchable properties are queried to find matching records. +When you generate a migration for these data models, the migrations include foreign key columns or pivot tables, based on the relationship's type. + +You want to create a relation between data models in the same module. + +You want to create a relationship between data models in different modules. Use module links instead. *** -## Define a Searchable Property +## One-to-One Relationship -Use the `searchable` method on a `text` property to indicate that it's searchable. +A one-to-one relationship indicates that one record of a data model belongs to or is associated with another. + +To define a one-to-one relationship, create relationship properties in the data models using the following methods: + +1. `hasOne`: indicates that the model has one record of the specified model. +2. `belongsTo`: indicates that the model belongs to one record of the specified model. For example: -```ts highlights={searchableHighlights} +```ts highlights={oneToOneHighlights} import { model } from "@medusajs/framework/utils" -const MyCustom = model.define("my_custom", { - name: model.text().searchable(), - // ... +const User = model.define("user", { + id: model.id().primaryKey(), + email: model.hasOne(() => Email), }) -export default MyCustom -``` - -In this example, the `name` property is searchable. - -### Search Example - -If you pass a `q` filter to the `listMyCustoms` method: - -```ts -const myCustoms = await helloModuleService.listMyCustoms({ - q: "John", -}) -``` - -This retrieves records that include `John` in their `name` property. - - -# Data Model Relationships - -In this chapter, you’ll learn how to define relationships between data models in your module. - -## What is a Relationship Property? - -A relationship property defines an association in the database between two models. It's created using the Data Model Language (DML) methods, such as `hasOne` or `belongsTo`. - -When you generate a migration for these data models, the migrations include foreign key columns or pivot tables, based on the relationship's type. - -You want to create a relation between data models in the same module. - -You want to create a relationship between data models in different modules. Use module links instead. - -*** - -## One-to-One Relationship - -A one-to-one relationship indicates that one record of a data model belongs to or is associated with another. - -To define a one-to-one relationship, create relationship properties in the data models using the following methods: - -1. `hasOne`: indicates that the model has one record of the specified model. -2. `belongsTo`: indicates that the model belongs to one record of the specified model. - -For example: - -```ts highlights={oneToOneHighlights} -import { model } from "@medusajs/framework/utils" - -const User = model.define("user", { - id: model.id().primaryKey(), - email: model.hasOne(() => Email), -}) - -const Email = model.define("email", { - id: model.id().primaryKey(), - user: model.belongsTo(() => User, { - mappedBy: "email", - }), -}) +const Email = model.define("email", { + id: model.id().primaryKey(), + user: model.belongsTo(() => User, { + mappedBy: "email", + }), +}) ``` In the example above, a user has one email, and an email belongs to one user. @@ -8665,6 +8609,50 @@ The `cascades` method accepts an object. Its key is the operation’s name, such In the example above, when a store is deleted, its associated products are also deleted. +# Searchable Data Model Property + +In this chapter, you'll learn what a searchable property is and how to define it. + +## What is a Searchable Property? + +Methods generated by the [service factory](https://docs.medusajs.com/learn/fundamentals/modules/service-factory/index.html.md) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters. + +When the `q` filter is passed, the data model's searchable properties are queried to find matching records. + +*** + +## Define a Searchable Property + +Use the `searchable` method on a `text` property to indicate that it's searchable. + +For example: + +```ts highlights={searchableHighlights} +import { model } from "@medusajs/framework/utils" + +const MyCustom = model.define("my_custom", { + name: model.text().searchable(), + // ... +}) + +export default MyCustom +``` + +In this example, the `name` property is searchable. + +### Search Example + +If you pass a `q` filter to the `listMyCustoms` method: + +```ts +const myCustoms = await helloModuleService.listMyCustoms({ + q: "John", +}) +``` + +This retrieves records that include `John` in their `name` property. + + # Write Migration In this chapter, you'll learn how to create a migration and write it manually. @@ -8743,6 +8731,51 @@ This rolls back the last ran migration on the Hello Module. To learn more about the Medusa CLI's database commands, refer to [this CLI reference](https://docs.medusajs.com/resources/medusa-cli/commands/db/index.html.md). +# Event Data Payload + +In this chapter, you'll learn how subscribers receive an event's data payload. + +## Access Event's Data Payload + +When events are emitted, they’re emitted with a data payload. + +The object that the subscriber function receives as a parameter has an `event` property, which is an object holding the event payload in a `data` property with additional context. + +For example: + +```ts title="src/subscribers/product-created.ts" highlights={highlights} collapsibleLines="1-5" expandButtonLabel="Show Imports" +import type { + SubscriberArgs, + SubscriberConfig, +} from "@medusajs/framework" + +export default async function productCreateHandler({ + event, +}: SubscriberArgs<{ id: string }>) { + const productId = event.data.id + console.log(`The product ${productId} was created`) +} + +export const config: SubscriberConfig = { + event: "product.created", +} +``` + +The `event` object has the following properties: + +- data: (\`object\`) The data payload of the event. Its properties are different for each event. +- name: (string) The name of the triggered event. +- metadata: (\`object\`) Additional data and context of the emitted event. + +This logs the product ID received in the `product.created` event’s data payload to the console. + +{/* --- + +## List of Events with Data Payload + +Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads. */} + + # Add Columns to a Link In this chapter, you'll learn how to add custom columns to a link definition and manage them. @@ -8891,248 +8924,232 @@ await link.create({ ``` -# Module Link Direction +# Emit Workflow and Service Events -In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case. +In this chapter, you'll learn about event types and how to emit an event in a service or workflow. -## Link Direction +## Event Types -The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`. +In your customization, you can emit an event, then listen to it in a subscriber and perform an asynchronus action, such as send a notification or data to a third-party system. -For example, the following defines a link from the `helloModuleService`'s `myCustom` data model to the Product Module's `product` data model: +There are two types of events in Medusa: -```ts -export default defineLink( - HelloModule.linkable.myCustom, - ProductModule.linkable.product -) -``` +1. Workflow event: an event that's emitted in a workflow after a commerce feature is performed. For example, Medusa emits the `order.placed` event after a cart is completed. +2. Service event: an event that's emitted to track, trace, or debug processes under the hood. For example, you can emit an event with an audit trail. -Whereas the following defines a link from the Product Module's `product` data model to the `helloModuleService`'s `myCustom` data model: +### Which Event Type to Use? -```ts -export default defineLink( - ProductModule.linkable.product, - HelloModule.linkable.myCustom -) -``` +**Workflow events** are the most common event type in development, as most custom features and customizations are built around workflows. -The above links are two different links that serve different purposes. +Some examples of workflow events: + +1. When a user creates a blog post and you're emitting an event to send a newsletter email. +2. When you finish syncing products to a third-party system and you want to notify the admin user of new products added. +3. When a customer purchases a digital product and you want to generate and send it to them. + +You should only go for a **service event** if you're emitting an event for processes under the hood that don't directly affect front-facing features. + +Some examples of service events: + +1. When you're tracing data manipulation and changes, and you want to track every time some custom data is changed. +2. When you're syncing data with a search engine. *** -## Which Link Direction to Use? +## Emit Event in a Workflow -### Extend Data Models +To emit a workflow event, use the `emitEventStep` helper step provided in the `@medusajs/medusa/core-flows` package. -If you're adding a link to a data model to extend it and add new fields, define the link from the main data model to the custom data model. +For example: -For example, consider you want to add a `subtitle` custom field to the `product` data model. To do that, you define a `Subtitle` data model in your module, then define a link from the `Product` data model to it: +```ts highlights={highlights} +import { + createWorkflow, +} from "@medusajs/framework/workflows-sdk" +import { + emitEventStep, +} from "@medusajs/medusa/core-flows" -```ts -export default defineLink( - ProductModule.linkable.product, - HelloModule.linkable.subtitle +const helloWorldWorkflow = createWorkflow( + "hello-world", + () => { + // ... + + emitEventStep({ + eventName: "custom.created", + data: { + id: "123", + // other data payload + }, + }) + } ) ``` -### Associate Data Models +The `emitEventStep` accepts an object having the following properties: -If you're linking data models to indicate an association between them, define the link from the custom data model to the main data model. +- `eventName`: The event's name. +- `data`: The data payload as an object. You can pass any properties in the object, and subscribers listening to the event will receive this data in the event's payload. -For example, consider you have `Post` data model representing a blog post, and you want to associate a blog post with a product. To do that, define a link from the `Post` data model to `Product`: +In this example, you emit the event `custom.created` and pass in the data payload an ID property. -```ts -export default defineLink( - HelloModule.linkable.post, - ProductModule.linkable.product -) -``` +### Test it Out +If you execute the workflow, the event is emitted and you can see it in your application's logs. -# Link +Any subscribers listening to the event are executed. -In this chapter, you’ll learn what Link is and how to use it to manage links. +*** -As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), Remote Link has been deprecated in favor of Link. They have the same usage, so you only need to change the key used to resolve the tool from the Medusa container as explained below. +## Emit Event in a Service -## What is Link? +To emit a service event: -Link is a class with utility methods to manage links between data models. It’s registered in the Medusa container under the `link` registration name. +1. Resolve `event_bus` from the module's container in your service's constructor: -For example: +### Extending Service Factory -```ts collapsibleLines="1-9" expandButtonLabel="Show Imports" -import { - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import { - ContainerRegistrationKeys, -} from "@medusajs/framework/utils" +```ts title="src/modules/hello/service.ts" highlights={["9"]} +import { IEventBusService } from "@medusajs/framework/types" +import { MedusaService } from "@medusajs/framework/utils" -export async function POST( - req: MedusaRequest, - res: MedusaResponse -): Promise { - const link = req.scope.resolve( - ContainerRegistrationKeys.LINK - ) - - // ... +class HelloModuleService extends MedusaService({ + MyCustom, +}){ + protected eventBusService_: AbstractEventBusModuleService + + constructor({ event_bus }) { + super(...arguments) + this.eventBusService_ = event_bus + } } ``` -You can use its methods to manage links, such as create or delete links. - -*** - -## Create Link - -To create a link between records of two data models, use the `create` method of Link. - -For example: +### Without Service Factory -```ts -import { Modules } from "@medusajs/framework/utils" +```ts title="src/modules/hello/service.ts" highlights={["6"]} +import { IEventBusService } from "@medusajs/framework/types" -// ... +class HelloModuleService { + protected eventBusService_: AbstractEventBusModuleService -await link.create({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, - "helloModuleService": { - my_custom_id: "mc_123", - }, -}) + constructor({ event_bus }) { + this.eventBusService_ = event_bus + } +} ``` -The `create` method accepts as a parameter an object. The object’s keys are the names of the linked modules. - -The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition. - -The value of each module’s property is an object, whose keys are of the format `{data_model_snake_name}_id`, and values are the IDs of the linked record. +2. Use the event bus service's `emit` method in the service's methods to emit an event: -So, in the example above, you link a record of the `MyCustom` data model in a `hello` module to a `Product` record in the Product Module. +```ts title="src/modules/hello/service.ts" highlights={serviceHighlights} +class HelloModuleService { + // ... + performAction() { + // TODO perform action -*** + this.eventBusService_.emit({ + name: "custom.event", + data: { + id: "123", + // other data payload + }, + }) + } +} +``` -## Dismiss Link +The method accepts an object having the following properties: -To remove a link between records of two data models, use the `dismiss` method of Link. +- `name`: The event's name. +- `data`: The data payload as an object. You can pass any properties in the object, and subscribers listening to the event will receive this data in the event's payload. -For example: +3. By default, the Event Module's service isn't injected into your module's container. To add it to the container, pass it in the module's registration object in `medusa-config.ts` in the `dependencies` property: -```ts +```ts title="medusa-config.ts" highlights={depsHighlight} import { Modules } from "@medusajs/framework/utils" -// ... - -await link.dismiss({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, - "helloModuleService": { - my_custom_id: "mc_123", - }, +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "./src/modules/hello", + dependencies: [ + Modules.EVENT_BUS, + ], + }, + ], }) ``` -The `dismiss` method accepts the same parameter type as the [create method](#create-link). - -The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition. - -*** - -## Cascade Delete Linked Records - -If a record is deleted, use the `delete` method of Link to delete all linked records. +The `dependencies` property accepts an array of module registration keys. The specified modules' main services are injected into the module's container. -For example: +That's how you can resolve it in your module's main service's constructor. -```ts -import { Modules } from "@medusajs/framework/utils" +### Test it Out -// ... +If you execute the `performAction` method of your service, the event is emitted and you can see it in your application's logs. -await productModuleService.deleteVariants([variant.id]) +Any subscribers listening to the event are also executed. -await link.delete({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, -}) -``` -This deletes all records linked to the deleted product. +# Module Link Direction -*** +In this chapter, you'll learn about the difference in module link directions, and which to use based on your use case. -## Restore Linked Records +## Link Direction -If a record that was previously soft-deleted is now restored, use the `restore` method of Link to restore all linked records. +The module link's direction depends on the order you pass the data model configuration parameters to `defineLink`. -For example: +For example, the following defines a link from the `helloModuleService`'s `myCustom` data model to the Product Module's `product` data model: ```ts -import { Modules } from "@medusajs/framework/utils" - -// ... - -await productModuleService.restoreProducts(["prod_123"]) - -await link.restore({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, -}) +export default defineLink( + HelloModule.linkable.myCustom, + ProductModule.linkable.product +) ``` +Whereas the following defines a link from the Product Module's `product` data model to the `helloModuleService`'s `myCustom` data model: -# Commerce Modules - -In this chapter, you'll learn about Medusa's commerce modules. - -## What is a Commerce Module? - -Commerce modules are built-in [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) of Medusa that provide core commerce logic specific to domains like Products, Orders, Customers, Fulfillment, and much more. - -Medusa's commerce modules are used to form Medusa's default [workflows](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md) and [APIs](https://docs.medusajs.com/api/store). For example, when you call the add to cart endpoint. the add to cart workflow runs which uses the Product Module to check if the product exists, the Inventory Module to ensure the product is available in the inventory, and the Cart Module to finally add the product to the cart. - -You'll find the details and steps of the add-to-cart workflow in [this workflow reference](https://docs.medusajs.com/resources/references/medusa-workflows/addToCartWorkflow/index.html.md) +```ts +export default defineLink( + ProductModule.linkable.product, + HelloModule.linkable.myCustom +) +``` -The core commerce logic contained in Commerce Modules is also available directly when you are building customizations. This granular access to commerce functionality is unique and expands what's possible to build with Medusa drastically. +The above links are two different links that serve different purposes. -### List of Medusa's Commerce Modules +*** -Refer to [this reference](https://docs.medusajs.com/resources/commerce-modules/index.html.md) for a full list of commerce modules in Medusa. +## Which Link Direction to Use? -*** +### Extend Data Models -## Use Commerce Modules in Custom Flows +If you're adding a link to a data model to extend it and add new fields, define the link from the main data model to the custom data model. -Similar to your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), the Medusa application registers a commerce module's service in the [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md). So, you can resolve it in your custom flows. This is useful as you build unique requirements extending core commerce features. +For example, consider you want to add a `subtitle` custom field to the `product` data model. To do that, you define a `Subtitle` data model in your module, then define a link from the `Product` data model to it: -For example, consider you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) (a special function that performs a task in a series of steps with rollback mechanism) that needs a step to retrieve the total number of products. You can create a step in the workflow that resolves the Product Module's service from the container to use its methods: +```ts +export default defineLink( + ProductModule.linkable.product, + HelloModule.linkable.subtitle +) +``` -```ts highlights={highlights} -import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" +### Associate Data Models -export const countProductsStep = createStep( - "count-products", - async ({ }, { container }) => { - const productModuleService = container.resolve("product") +If you're linking data models to indicate an association between them, define the link from the custom data model to the main data model. - const [,count] = await productModuleService.listAndCountProducts() +For example, consider you have `Post` data model representing a blog post, and you want to associate a blog post with a product. To do that, define a link from the `Post` data model to `Product`: - return new StepResponse(count) - } +```ts +export default defineLink( + HelloModule.linkable.post, + ProductModule.linkable.product ) ``` -Your workflow can use services of both custom and commerce modules, supporting you in building custom flows without having to re-build core commerce features. - # Query Context @@ -9360,42 +9377,151 @@ In this example, you retrieve products and their associated posts. You also pass To handle the context, you override the generated `listPosts` method of the Blog Module as explained [previously](#how-to-use-query-context). -# Architectural Modules - -In this chapter, you’ll learn about architectural modules. +# Link -## What is an Architectural Module? +In this chapter, you’ll learn what Link is and how to use it to manage links. -An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure. +As of [Medusa v2.2.0](https://github.com/medusajs/medusa/releases/tag/v2.2.0), Remote Link has been deprecated in favor of Link. They have the same usage, so you only need to change the key used to resolve the tool from the Medusa container as explained below. -Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis. +## What is Link? -*** +Link is a class with utility methods to manage links between data models. It’s registered in the Medusa container under the `link` registration name. -## Architectural Module Types +For example: -There are different architectural module types including: +```ts collapsibleLines="1-9" expandButtonLabel="Show Imports" +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import { + ContainerRegistrationKeys, +} from "@medusajs/framework/utils" -![Diagram illustrating how the modules connect to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1727095814/Medusa%20Book/architectural-modules_bj9bb9.jpg) +export async function POST( + req: MedusaRequest, + res: MedusaResponse +): Promise { + const link = req.scope.resolve( + ContainerRegistrationKeys.LINK + ) + + // ... +} +``` -- Cache Module: Defines the caching mechanism or logic to cache computational results. -- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events. -- Workflow Engine Module: Integrates a service to store and track workflow executions and steps. -- File Module: Integrates a storage service to handle uploading and managing files. -- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers. +You can use its methods to manage links, such as create or delete links. *** -## Architectural Modules List - -Refer to the [Architectural Modules reference](https://docs.medusajs.com/resources/architectural-modules/index.html.md) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module. +## Create Link +To create a link between records of two data models, use the `create` method of Link. -# Query +For example: -In this chapter, you’ll learn about Query and how to use it to fetch data from modules. +```ts +import { Modules } from "@medusajs/framework/utils" -## What is Query? +// ... + +await link.create({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, + "helloModuleService": { + my_custom_id: "mc_123", + }, +}) +``` + +The `create` method accepts as a parameter an object. The object’s keys are the names of the linked modules. + +The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition. + +The value of each module’s property is an object, whose keys are of the format `{data_model_snake_name}_id`, and values are the IDs of the linked record. + +So, in the example above, you link a record of the `MyCustom` data model in a `hello` module to a `Product` record in the Product Module. + +*** + +## Dismiss Link + +To remove a link between records of two data models, use the `dismiss` method of Link. + +For example: + +```ts +import { Modules } from "@medusajs/framework/utils" + +// ... + +await link.dismiss({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, + "helloModuleService": { + my_custom_id: "mc_123", + }, +}) +``` + +The `dismiss` method accepts the same parameter type as the [create method](#create-link). + +The keys (names of linked modules) must be in the same [direction](https://docs.medusajs.com/learn/fundamentals/module-links/directions/index.html.md) of the link definition. + +*** + +## Cascade Delete Linked Records + +If a record is deleted, use the `delete` method of Link to delete all linked records. + +For example: + +```ts +import { Modules } from "@medusajs/framework/utils" + +// ... + +await productModuleService.deleteVariants([variant.id]) + +await link.delete({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, +}) +``` + +This deletes all records linked to the deleted product. + +*** + +## Restore Linked Records + +If a record that was previously soft-deleted is now restored, use the `restore` method of Link to restore all linked records. + +For example: + +```ts +import { Modules } from "@medusajs/framework/utils" + +// ... + +await productModuleService.restoreProducts(["prod_123"]) + +await link.restore({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, +}) +``` + + +# Query + +In this chapter, you’ll learn about Query and how to use it to fetch data from modules. + +## What is Query? Query fetches data across modules. It’s a set of methods registered in the Medusa container under the `query` key. @@ -9745,414 +9871,486 @@ Try passing one of the Query configuration parameters, like `fields` or `limit`, Learn more about [specifing fields and relations](https://docs.medusajs.com/api/store#select-fields-and-relations) and [pagination](https://docs.medusajs.com/api/store#pagination) in the API reference. -# Module Isolation +# Create a Plugin -In this chapter, you'll learn how modules are isolated, and what that means for your custom development. +In this chapter, you'll learn how to create a Medusa plugin and publish it. -- Modules can't access resources, such as services or data models, from other modules. -- Use Medusa's linking concepts, as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), to extend a module's data models and retrieve data across modules. +A [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md) is a package of reusable Medusa customizations that you can install in any Medusa application. By creating and publishing a plugin, you can reuse your Medusa customizations across multiple projects or share them with the community. -## How are Modules Isolated? +Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0). -A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module. +## 1. Create a Plugin Project -For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models. +Plugins are created in a separate Medusa project. This makes the development and publishing of the plugin easier. Later, you'll install that plugin in your Medusa application to test it out and use it. -*** +Medusa's `create-medusa-app` CLI tool provides the option to create a plugin project. Run the following command to create a new plugin project: -## Why are Modules Isolated +```bash +npx create-medusa-app my-plugin --plugin +``` -Some of the module isolation's benefits include: +This will create a new Medusa plugin project in the `my-plugin` directory. -- Integrate your module into any Medusa application without side-effects to your setup. -- Replace existing modules with your custom implementation, if your use case is drastically different. -- Use modules in other environments, such as Edge functions and Next.js apps. +### Plugin Directory Structure -*** +After the installation is done, the plugin structure will look like this: -## How to Extend Data Model of Another Module? +![Directory structure of a plugin project](https://res.cloudinary.com/dza7lstvk/image/upload/v1737019441/Medusa%20Book/project-dir_q4xtri.jpg) -To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md). +- `src/`: Contains the Medusa customizations. +- `src/admin`: Contains [admin extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md). +- `src/api`: Contains [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) and [middlewares](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md). You can add store, admin, or any custom API routes. +- `src/jobs`: Contains [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md). +- `src/links`: Contains [module links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md). +- `src/modules`: Contains [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). +- `src/provider`: Contains [module providers](#create-module-providers). +- `src/subscribers`: Contains [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md). +- `src/workflows`: Contains [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). You can also add [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md) under `src/workflows/hooks`. +- `package.json`: Contains the plugin's package information, including general information and dependencies. +- `tsconfig.json`: Contains the TypeScript configuration for the plugin. *** -## How to Use Services of Other Modules? +## 2. Prepare Plugin -If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities. +Before developing, testing, and publishing your plugin, make sure its name in `package.json` is correct. This is the name you'll use to install the plugin in your Medusa application. -Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output. +For example: -### Example +```json title="package.json" +{ + "name": "@myorg/plugin-name", + // ... +} +``` -For example, consider you have two modules: +In addition, make sure that the `keywords` field in `package.json` includes the keyword `medusa-plugin` and `medusa-v2`. This helps Medusa list community plugins on the Medusa website: -1. A module that stores and manages brands in your application. -2. A module that integrates a third-party Content Management System (CMS). +```json title="package.json" +{ + "keywords": [ + "medusa-plugin", + "medusa-v2" + ], + // ... +} +``` -To sync brands from your application to the third-party system, create the following steps: +*** -```ts title="Example Steps" highlights={stepsHighlights} -const retrieveBrandsStep = createStep( - "retrieve-brands", - async (_, { container }) => { - const brandModuleService = container.resolve( - "brandModuleService" - ) +## 3. Publish Plugin Locally for Development and Testing - const brands = await brandModuleService.listBrands() +Medusa's CLI tool provides commands to simplify developing and testing your plugin in a local Medusa application. You start by publishing your plugin in the local package registry, then install it in your Medusa application. You can then watch for changes in the plugin as you develop it. - return new StepResponse(brands) - } -) +### Publish and Install Local Package -const createBrandsInCmsStep = createStep( - "create-brands-in-cms", - async ({ brands }, { container }) => { - const cmsModuleService = container.resolve( - "cmsModuleService" - ) +### Prerequisites - const cmsBrands = await cmsModuleService.createBrands(brands) +- [Medusa application installed.](https://docs.medusajs.com/learn/installation/index.html.md) - return new StepResponse(cmsBrands, cmsBrands) - }, - async (brands, { container }) => { - const cmsModuleService = container.resolve( - "cmsModuleService" - ) +The first time you create your plugin, you need to publish the package into a local package registry, then install it in your Medusa application. This is a one-time only process. - await cmsModuleService.deleteBrands( - brands.map((brand) => brand.id) - ) - } -) -``` +To publish the plugin to the local registry, run the following command in your plugin project: -The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module. +```bash title="Plugin project" +npx medusa plugin:publish +``` -Then, create the following workflow that uses these steps: +This command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. The plugin is published locally under the name you specified in `package.json`. -```ts title="Example Workflow" -export const syncBrandsWorkflow = createWorkflow( - "sync-brands", - () => { - const brands = retrieveBrandsStep() +Next, navigate to your Medusa application: - createBrandsInCmsStep({ brands }) - } -) +```bash title="Medusa application" +cd ~/path/to/medusa-app ``` -You can then use this workflow in an API route, scheduled job, or other resources that use this functionality. +Make sure to replace `~/path/to/medusa-app` with the path to your Medusa application. +Then, if your project was created before v2.3.1 of Medusa, make sure to install `yalc` as a development dependency: -# Module Container +```bash npm2yarn title="Medusa application" +npm install --save-dev yalc +``` -In this chapter, you'll learn about the module's container and how to resolve resources in that container. +After that, run the following Medusa CLI command to install the plugin: -Since modules are isolated, each module has a local container only used by the resources of that module. +```bash title="Medusa application" +npx medusa plugin:add @myorg/plugin-name +``` -So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container. +Make sure to replace `@myorg/plugin-name` with the name of your plugin as specified in `package.json`. Your plugin will be installed from the local package registry into your Medusa application. -### List of Registered Resources +### Register Plugin in Medusa Application -Find a list of resources or dependencies registered in a module's container in [this Development Resources reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md). +After installing the plugin, you need to register it in your Medusa application in the configurations defined in `medusa-config.ts`. -*** +Add the plugin to the `plugins` array in the `medusa-config.ts` file: -## Resolve Resources +```ts title="medusa-config.ts" highlights={pluginHighlights} +module.exports = defineConfig({ + // ... + plugins: [ + { + resolve: "@myorg/plugin-name", + options: {}, + }, + ], +}) +``` -### Services +The `plugins` configuration is an array of objects where each object has a `resolve` key whose value is the name of the plugin package. -A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container. - -For example: - -```ts highlights={[["4"], ["10"]]} -import { Logger } from "@medusajs/framework/types" - -type InjectedDependencies = { - logger: Logger -} - -export default class HelloModuleService { - protected logger_: Logger +#### Pass Module Options through Plugin - constructor({ logger }: InjectedDependencies) { - this.logger_ = logger +Each plugin configuration also accepts an `options` property, whose value is an object of options to pass to the plugin's modules. - this.logger_.info("[HelloModuleService]: Hello World!") - } +For example: +```ts title="medusa-config.ts" highlights={pluginOptionsHighlight} +module.exports = defineConfig({ // ... -} + plugins: [ + { + resolve: "@myorg/plugin-name", + options: { + apiKey: true, + }, + }, + ], +}) ``` -### Loader - -A loader function accepts as a parameter an object having the property `container`. Its value is the module's container used to resolve resources. +The `options` property in the plugin configuration is passed to all modules in the plugin. Learn more about module options in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md). -For example: +### Watch Plugin Changes During Development -```ts highlights={[["9"]]} -import { - LoaderOptions, -} from "@medusajs/framework/types" -import { - ContainerRegistrationKeys, -} from "@medusajs/framework/utils" +While developing your plugin, you can watch for changes in the plugin and automatically update the plugin in the Medusa application using it. This is the only command you'll continuously need during your plugin development. -export default async function helloWorldLoader({ - container, -}: LoaderOptions) { - const logger = container.resolve(ContainerRegistrationKeys.LOGGER) +To do that, run the following command in your plugin project: - logger.info("[helloWorldLoader]: Hello, World!") -} +```bash title="Plugin project" +npx medusa plugin:develop ``` +This command will: -# Loaders +- Watch for changes in the plugin. Whenever a file is changed, the plugin is automatically built. +- Publish the plugin changes to the local package registry. This will automatically update the plugin in the Medusa application using it. You can also benefit from real-time HMR updates of admin extensions. -In this chapter, you’ll learn about loaders and how to use them. +### Start Medusa Application -## What is a Loader? +You can start your Medusa application's development server to test out your plugin: -When building a commerce application, you'll often need to execute an action the first time the application starts. For example, if your application needs to connect to databases other than Medusa's PostgreSQL database, you might need to establish a connection on application startup. +```bash npm2yarn title="Medusa application" +npm run dev +``` -In Medusa, you can execute an action when the application starts using a loader. A loader is a function exported by a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of business logic for a single domain. When the Medusa application starts, it executes all loaders exported by configured modules. +While your Medusa application is running and the plugin is being watched, you can test your plugin while developing it in the Medusa application. -Loaders are useful to register custom resources, such as database connections, in the [module's container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md), which is similar to the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) but includes only [resources available to the module](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). Modules are isolated, so they can't access resources outside of them, such as a service in another module. +*** -Medusa isolates modules to ensure that they're re-usable across applications, aren't tightly coupled to other resources, and don't have implications when integrated into the Medusa application. Learn more about why modules are isolated in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), and check out [this reference for the list of resources in the module's container](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). +## 4. Create Customizations in the Plugin -*** +You can now build your plugin's customizations. The following guide explains how to build different customizations in your plugin. -## How to Create a Loader? +- [Create a module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) +- [Create a module link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) +- [Create a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) +- [Add a workflow hook](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md) +- [Create an API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) +- [Add a subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) +- [Add a scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) +- [Add an admin widget](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md) +- [Add an admin UI route](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md) -### 1. Implement Loader Function +While building those customizations, you can test them in your Medusa application by [watching the plugin changes](#watch-plugin-changes-during-development) and [starting the Medusa application](#start-medusa-application). -You create a loader function in a TypeScript or JavaScript file under a module's `loaders` directory. +### Generating Migrations for Modules -For example, consider you have a `hello` module, you can create a loader at `src/modules/hello/loaders/hello-world.ts` with the following content: +During your development, you may need to generate migrations for modules in your plugin. To do that, use the `plugin:db:generate` command: -![Example of loader file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732865671/Medusa%20Book/loader-dir-overview_eg6vtu.jpg) +```bash title="Plugin project" +npx medusa plugin:db:generate +``` -Learn how to create a module in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). +This command generates migrations for all modules in the plugin. You can then run these migrations on the Medusa application that the plugin is installed in using the `db:migrate` command: -```ts title="src/modules/hello/loaders/hello-world.ts" -import { - LoaderOptions, -} from "@medusajs/framework/types" +```bash title="Medusa application" +npx medusa db:migrate +``` -export default async function helloWorldLoader({ - container, -}: LoaderOptions) { - const logger = container.resolve("logger") +### Importing Module Resources - logger.info("[helloWorldLoader]: Hello, World!") +Your plugin project should have the following exports in `package.json`: + +```json title="package.json" +{ + "exports": { + "./package.json": "./package.json", + "./workflows": "./.medusa/server/src/workflows/index.js", + "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js", + "./providers/*": "./.medusa/server/src/providers/*/index.js", + "./*": "./.medusa/server/src/*.js" + } } ``` -The loader file exports an async function, which is the function executed when the application loads. +Aside from the `./package.json` and `./providers`, these exports are only a recommendation. You can cherry-pick the files and directories you want to export. -The function receives an object parameter that has a `container` property, which is the module's container that you can use to resolve resources from. In this example, you resolve the Logger utility to log a message in the terminal. +The plugin exports the following files and directories: -Find the list of resources in the module's container in [this reference](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). +- `./package.json`: The package.json file. Medusa needs to access the `package.json` when registering the plugin. +- `./workflows`: The workflows exported in `./src/workflows/index.ts`. +- `./.medusa/server/src/modules/*`: The definition file of modules. This is useful if you create links to the plugin's modules in the Medusa application. +- `./providers/*`: The definition file of module providers. This allows you to register the plugin's providers in the Medusa application. +- `./*`: Any other files in the plugin's `src` directory. -### 2. Export Loader in Module Definition +With these exports, you can import the plugin's resources in the Medusa application's code like this: -After implementing the loader, you must export it in the module's definition in the `index.ts` file at the root of the module's directory. Otherwise, the Medusa application will not run it. +`@myorg/plugin-name` is the plugin package's name. -So, to export the loader you implemented above in the `hello` module, add the following to `src/modules/hello/index.ts`: +```ts +import { Workflow1, Workflow2 } from "@myorg/plugin-name/workflows" +import BlogModule from "@myorg/plugin-name/modules/blog" +// import other files created in plugin like ./src/types/blog.ts +import BlogType from "@myorg/plugin-name/types/blog" +``` -```ts title="src/modules/hello/index.ts" -// other imports... -import helloWorldLoader from "./loaders/hello-world" +And you can register a module provider in the Medusa application's `medusa-config.ts` like this: -export default Module("hello", { +```ts highlights={[["9"]]} title="medusa-config.ts" +module.exports = defineConfig({ // ... - loaders: [helloWorldLoader], + modules: [ + { + resolve: "@medusajs/medusa/notification", + options: { + providers: [ + { + resolve: "@myorg/plugin-name/providers/my-notification", + id: "my-notification", + options: { + channels: ["email"], + // provider options... + }, + }, + ], + }, + }, + ], }) ``` -The second parameter of the `Module` function accepts a `loaders` property whose value is an array of loader functions. The Medusa application will execute these functions when it starts. +You pass to `resolve` the path to the provider relative to the plugin package. So, in this example, the `my-notification` provider is located in `./src/providers/my-notification/index.ts` of the plugin. -### Test the Loader +### Create Module Providers -Assuming your module is [added to Medusa's configuration](https://docs.medusajs.com/learn/fundamentals/modules#4-add-module-to-medusas-configurations/index.html.md), you can test the loader by starting the Medusa application: +To learn how to create module providers, refer to the following guides: -```bash npm2yarn -npm run dev -``` +- [File Module Provider](https://docs.medusajs.com/resources/references/file-provider-module/index.html.md) +- [Notification Module Provider](https://docs.medusajs.com/resources/references/notification-provider-module/index.html.md) +- [Auth Module Provider](https://docs.medusajs.com/resources/references/auth/provider/index.html.md) +- [Payment Module Provider](https://docs.medusajs.com/resources/references/payment/provider/index.html.md) +- [Fulfillment Module Provider](https://docs.medusajs.com/resources/references/fulfillment/provider/index.html.md) +- [Tax Module Provider](https://docs.medusajs.com/resources/references/tax/provider/index.html.md) -Then, you'll find the following message logged in the terminal: +*** -```plain -info: [HELLO MODULE] Just started the Medusa application! +## 5. Publish Plugin to NPM + +Medusa's CLI tool provides a command that bundles your plugin to be published to npm. Once you're ready to publish your plugin publicly, run the following command in your plugin project: + +```bash +npx medusa plugin:build ``` -This indicates that the loader in the `hello` module ran and logged this message. +The command will compile an output in the `.medusa/server` directory. -*** +You can now publish the plugin to npm using the [NPM CLI tool](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Run the following command to publish the plugin to npm: -## Example: Register Custom MongoDB Connection +```bash +npm publish +``` -As mentioned in this chapter's introduction, loaders are most useful when you need to register a custom resource in the module's container to re-use it in other customizations in the module. +If you haven't logged in before with your NPM account, you'll be asked to log in first. Then, your package is published publicly to be used in any Medusa application. -Consider your have a MongoDB module that allows you to perform operations on a MongoDB database. +### Install Public Plugin in Medusa Application -### Prerequisites +You install a plugin that's published publicly using your package manager. For example: -- [MongoDB database that you can connect to from a local machine.](https://www.mongodb.com) -- [Install the MongoDB SDK in your Medusa application.](https://www.mongodb.com/docs/drivers/node/current/quick-start/download-and-install/#install-the-node.js-driver) +```bash npm2yarn +npm install @myorg/plugin-name +``` -To connect to the database, you create the following loader in your module: +Where `@myorg/plugin-name` is the name of your plugin as published on NPM. -```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights} -import { LoaderOptions } from "@medusajs/framework/types" -import { asValue } from "awilix" -import { MongoClient } from "mongodb" +Then, register the plugin in your Medusa application's configurations as explained in [this section](#register-plugin-in-medusa-application). -type ModuleOptions = { - connection_url?: string - db_name?: string -} +*** -export default async function mongoConnectionLoader({ - container, - options, -}: LoaderOptions) { - if (!options.connection_url) { - throw new Error(`[MONGO MDOULE]: connection_url option is required.`) - } - if (!options.db_name) { - throw new Error(`[MONGO MDOULE]: db_name option is required.`) - } - const logger = container.resolve("logger") - - try { - const clientDb = ( - await (new MongoClient(options.connection_url)).connect() - ).db(options.db_name) +## Update a Published Plugin - logger.info("Connected to MongoDB") +To update the Medusa dependencies in a plugin, refer to [this documentation](https://docs.medusajs.com/learn/update#update-plugin-project/index.html.md). - container.register( - "mongoClient", - asValue(clientDb) - ) - } catch (e) { - logger.error( - `[MONGO MDOULE]: An error occurred while connecting to MongoDB: ${e}` - ) - } -} -``` +If you've published a plugin and you've made changes to it, you'll have to publish the update to NPM again. -The loader function accepts in its object parameter an `options` property, which is the options passed to the module in Medusa's configurations. For example: +First, run the following command to change the version of the plugin: -```ts title="medusa-config.ts" highlights={optionHighlights} -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "./src/modules/mongo", - options: { - connection_url: process.env.MONGO_CONNECTION_URL, - db_name: process.env.MONGO_DB_NAME, - }, - }, - ], -}) +```bash +npm version ``` -Passing options is useful when your module needs informations like connection URLs or API keys, as it ensures your module can be re-usable across applications. For the MongoDB Module, you expect two options: +Where `` indicates the type of version update you’re publishing. For example, it can be `major` or `minor`. Refer to the [npm version documentation](https://docs.npmjs.com/cli/v10/commands/npm-version) for more information. -- `connection_url`: the URL to connect to the MongoDB database. -- `db_name`: The name of the database to connect to. +Then, re-run the same commands for publishing a plugin: -In the loader, you check first that these options are set before proceeding. Then, you create an instance of the MongoDB client and connect to the database specified in the options. +```bash +npx medusa plugin:build +npm publish +``` -After creating the client, you register it in the module's container using the container's `register` method. The method accepts two parameters: +This will publish an updated version of your plugin under a new version. -1. The key to register the resource under, which in this case is `mongoClient`. You'll use this name later to resolve the client. -2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [awilix package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa. -### Use Custom Registered Resource in Module's Service +# Architectural Modules -After registering the custom MongoDB client in the module's container, you can now resolve and use it in the module's service. +In this chapter, you’ll learn about architectural modules. + +## What is an Architectural Module? + +An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure. + +Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis. + +*** + +## Architectural Module Types + +There are different architectural module types including: + +![Diagram illustrating how the modules connect to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1727095814/Medusa%20Book/architectural-modules_bj9bb9.jpg) + +- Cache Module: Defines the caching mechanism or logic to cache computational results. +- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events. +- Workflow Engine Module: Integrates a service to store and track workflow executions and steps. +- File Module: Integrates a storage service to handle uploading and managing files. +- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers. + +*** + +## Architectural Modules List + +Refer to the [Architectural Modules reference](https://docs.medusajs.com/resources/architectural-modules/index.html.md) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module. + + +# Module Container + +In this chapter, you'll learn about the module's container and how to resolve resources in that container. + +Since modules are isolated, each module has a local container only used by the resources of that module. + +So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container. + +### List of Registered Resources + +Find a list of resources or dependencies registered in a module's container in [this Development Resources reference](https://docs.medusajs.com/resources/medusa-container-resources/index.html.md). + +*** + +## Resolve Resources + +### Services + +A service's constructor accepts as a first parameter an object used to resolve resources registered in the module's container. For example: -```ts title="src/modules/mongo/service.ts" -import type { Db } from "mongodb" +```ts highlights={[["4"], ["10"]]} +import { Logger } from "@medusajs/framework/types" type InjectedDependencies = { - mongoClient: Db + logger: Logger } -export default class MongoModuleService { - private mongoClient_: Db +export default class HelloModuleService { + protected logger_: Logger - constructor({ mongoClient }: InjectedDependencies) { - this.mongoClient_ = mongoClient + constructor({ logger }: InjectedDependencies) { + this.logger_ = logger + + this.logger_.info("[HelloModuleService]: Hello World!") } - async createMovie({ title }: { - title: string - }) { - const moviesCol = this.mongoClient_.collection("movie") + // ... +} +``` - const insertedMovie = await moviesCol.insertOne({ - title, - }) +### Loader - const movie = await moviesCol.findOne({ - _id: insertedMovie.insertedId, - }) +A loader function accepts as a parameter an object having the property `container`. Its value is the module's container used to resolve resources. - return movie - } +For example: - async deleteMovie(id: string) { - const moviesCol = this.mongoClient_.collection("movie") +```ts highlights={[["9"]]} +import { + LoaderOptions, +} from "@medusajs/framework/types" +import { + ContainerRegistrationKeys, +} from "@medusajs/framework/utils" - await moviesCol.deleteOne({ - _id: { - equals: id, - }, - }) - } +export default async function helloWorldLoader({ + container, +}: LoaderOptions) { + const logger = container.resolve(ContainerRegistrationKeys.LOGGER) + + logger.info("[helloWorldLoader]: Hello, World!") } ``` -The service `MongoModuleService` resolves the `mongoClient` resource you registered in the loader and sets it as a class property. You then use it in the `createMovie` and `deleteMovie` methods, which create and delete a document in a `movie` collection in the MongoDB database, respectively. -Make sure to export the loader in the module's definition in the `index.ts` file at the root directory of the module: +# Commerce Modules -```ts title="src/modules/mongo/index.ts" highlights={[["9"]]} -import { Module } from "@medusajs/framework/utils" -import MongoModuleService from "./service" -import mongoConnectionLoader from "./loaders/connection" +In this chapter, you'll learn about Medusa's commerce modules. -export const MONGO_MODULE = "mongo" +## What is a Commerce Module? -export default Module(MONGO_MODULE, { - service: MongoModuleService, - loaders: [mongoConnectionLoader], -}) -``` +Commerce modules are built-in [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) of Medusa that provide core commerce logic specific to domains like Products, Orders, Customers, Fulfillment, and much more. -### Test it Out +Medusa's commerce modules are used to form Medusa's default [workflows](https://docs.medusajs.com/resources/medusa-workflows-reference/index.html.md) and [APIs](https://docs.medusajs.com/api/store). For example, when you call the add to cart endpoint. the add to cart workflow runs which uses the Product Module to check if the product exists, the Inventory Module to ensure the product is available in the inventory, and the Cart Module to finally add the product to the cart. -You can test the connection out by starting the Medusa application. If it's successful, you'll see the following message logged in the terminal: +You'll find the details and steps of the add-to-cart workflow in [this workflow reference](https://docs.medusajs.com/resources/references/medusa-workflows/addToCartWorkflow/index.html.md) -```bash -info: Connected to MongoDB +The core commerce logic contained in Commerce Modules is also available directly when you are building customizations. This granular access to commerce functionality is unique and expands what's possible to build with Medusa drastically. + +### List of Medusa's Commerce Modules + +Refer to [this reference](https://docs.medusajs.com/resources/commerce-modules/index.html.md) for a full list of commerce modules in Medusa. + +*** + +## Use Commerce Modules in Custom Flows + +Similar to your [custom modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), the Medusa application registers a commerce module's service in the [container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md). So, you can resolve it in your custom flows. This is useful as you build unique requirements extending core commerce features. + +For example, consider you have a [workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) (a special function that performs a task in a series of steps with rollback mechanism) that needs a step to retrieve the total number of products. You can create a step in the workflow that resolves the Product Module's service from the container to use its methods: + +```ts highlights={highlights} +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" + +export const countProductsStep = createStep( + "count-products", + async ({ }, { container }) => { + const productModuleService = container.resolve("product") + + const [,count] = await productModuleService.listAndCountProducts() + + return new StepResponse(count) + } +) ``` -You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database. +Your workflow can use services of both custom and commerce modules, supporting you in building custom flows without having to re-build core commerce features. # Perform Database Operations in a Service @@ -10576,463 +10774,644 @@ class HelloModuleService { ``` -# Multiple Services in a Module +# Module Isolation -In this chapter, you'll learn how to use multiple services in a module. +In this chapter, you'll learn how modules are isolated, and what that means for your custom development. -## Module's Main and Internal Services +- Modules can't access resources, such as services or data models, from other modules. +- Use Medusa's linking concepts, as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md), to extend a module's data models and retrieve data across modules. -A module has one main service only, which is the service exported in the module's definition. +## How are Modules Isolated? -However, you may use other services in your module to better organize your code or split functionalities. These are called internal services that can be resolved within your module, but not in external resources. +A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module. + +For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models. *** -## How to Add an Internal Service +## Why are Modules Isolated -### 1. Create Service +Some of the module isolation's benefits include: -To add an internal service, create it in the `services` directory of your module. +- Integrate your module into any Medusa application without side-effects to your setup. +- Replace existing modules with your custom implementation, if your use case is drastically different. +- Use modules in other environments, such as Edge functions and Next.js apps. -For example, create the file `src/modules/hello/services/client.ts` with the following content: +*** -```ts title="src/modules/hello/services/client.ts" -export class ClientService { - async getMessage(): Promise { - return "Hello, World!" - } -} -``` +## How to Extend Data Model of Another Module? -### 2. Export Service in Index +To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md). -Next, create an `index.ts` file under the `services` directory of the module that exports your internal services. +*** -For example, create the file `src/modules/hello/services/index.ts` with the following content: +## How to Use Services of Other Modules? -```ts title="src/modules/hello/services/index.ts" -export * from "./client" -``` +If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities. -This exports the `ClientService`. +Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output. -### 3. Resolve Internal Service +### Example -Internal services exported in the `services/index.ts` file of your module are now registered in the container and can be resolved in other services in the module as well as loaders. +For example, consider you have two modules: -For example, in your main service: +1. A module that stores and manages brands in your application. +2. A module that integrates a third-party Content Management System (CMS). -```ts title="src/modules/hello/service.ts" highlights={[["5"], ["13"]]} -// other imports... -import { ClientService } from "./services" +To sync brands from your application to the third-party system, create the following steps: -type InjectedDependencies = { - clientService: ClientService -} +```ts title="Example Steps" highlights={stepsHighlights} +const retrieveBrandsStep = createStep( + "retrieve-brands", + async (_, { container }) => { + const brandModuleService = container.resolve( + "brandModuleService" + ) -class HelloModuleService extends MedusaService({ - MyCustom, -}){ - protected clientService_: ClientService + const brands = await brandModuleService.listBrands() - constructor({ clientService }: InjectedDependencies) { - super(...arguments) - this.clientService_ = clientService + return new StepResponse(brands) } -} -``` - -You can now use your internal service in your main service. +) -*** +const createBrandsInCmsStep = createStep( + "create-brands-in-cms", + async ({ brands }, { container }) => { + const cmsModuleService = container.resolve( + "cmsModuleService" + ) -## Resolve Resources in Internal Service + const cmsBrands = await cmsModuleService.createBrands(brands) -Resolve dependencies from your module's container in the constructor of your internal service. + return new StepResponse(cmsBrands, cmsBrands) + }, + async (brands, { container }) => { + const cmsModuleService = container.resolve( + "cmsModuleService" + ) -For example: + await cmsModuleService.deleteBrands( + brands.map((brand) => brand.id) + ) + } +) +``` -```ts -import { Logger } from "@medusajs/framework/types" +The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module. -type InjectedDependencies = { - logger: Logger -} +Then, create the following workflow that uses these steps: -export class ClientService { - protected logger_: Logger +```ts title="Example Workflow" +export const syncBrandsWorkflow = createWorkflow( + "sync-brands", + () => { + const brands = retrieveBrandsStep() - constructor({ logger }: InjectedDependencies) { - this.logger_ = logger + createBrandsInCmsStep({ brands }) } -} +) ``` -*** +You can then use this workflow in an API route, scheduled job, or other resources that use this functionality. -## Access Module Options -Your internal service can't access the module's options. +# Loaders -To retrieve the module's options, use the `configModule` registered in the module's container, which is the configurations in `medusa-config.ts`. +In this chapter, you’ll learn about loaders and how to use them. -For example: +## What is a Loader? -```ts -import { ConfigModule } from "@medusajs/framework/types" -import { HELLO_MODULE } from ".." +When building a commerce application, you'll often need to execute an action the first time the application starts. For example, if your application needs to connect to databases other than Medusa's PostgreSQL database, you might need to establish a connection on application startup. -export type InjectedDependencies = { - configModule: ConfigModule -} +In Medusa, you can execute an action when the application starts using a loader. A loader is a function exported by a [module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md), which is a package of business logic for a single domain. When the Medusa application starts, it executes all loaders exported by configured modules. -export class ClientService { - protected options: Record +Loaders are useful to register custom resources, such as database connections, in the [module's container](https://docs.medusajs.com/learn/fundamentals/modules/container/index.html.md), which is similar to the [Medusa container](https://docs.medusajs.com/learn/fundamentals/medusa-container/index.html.md) but includes only [resources available to the module](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). Modules are isolated, so they can't access resources outside of them, such as a service in another module. - constructor({ configModule }: InjectedDependencies) { - const moduleDef = configModule.modules[HELLO_MODULE] +Medusa isolates modules to ensure that they're re-usable across applications, aren't tightly coupled to other resources, and don't have implications when integrated into the Medusa application. Learn more about why modules are isolated in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md), and check out [this reference for the list of resources in the module's container](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). - if (typeof moduleDef !== "boolean") { - this.options = moduleDef.options - } - } -} -``` +*** -The `configModule` has a `modules` property that includes all registered modules. Retrieve the module's configuration using its registration key. +## How to Create a Loader? -If its value is not a `boolean`, set the service's options to the module configuration's `options` property. +### 1. Implement Loader Function +You create a loader function in a TypeScript or JavaScript file under a module's `loaders` directory. -# Modules Directory Structure +For example, consider you have a `hello` module, you can create a loader at `src/modules/hello/loaders/hello-world.ts` with the following content: -In this document, you'll learn about the expected files and directories in your module. +![Example of loader file in the application's directory structure](https://res.cloudinary.com/dza7lstvk/image/upload/v1732865671/Medusa%20Book/loader-dir-overview_eg6vtu.jpg) -![Module Directory Structure Example](https://res.cloudinary.com/dza7lstvk/image/upload/v1714379976/Medusa%20Book/modules-dir-overview_nqq7ne.jpg) +Learn how to create a module in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). -## index.ts +```ts title="src/modules/hello/loaders/hello-world.ts" +import { + LoaderOptions, +} from "@medusajs/framework/types" -The `index.ts` file in the root of your module's directory is the only required file. It must export the module's definition as explained in a [previous chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). +export default async function helloWorldLoader({ + container, +}: LoaderOptions) { + const logger = container.resolve("logger") -*** + logger.info("[helloWorldLoader]: Hello, World!") +} +``` -## service.ts +The loader file exports an async function, which is the function executed when the application loads. -A module must have a main service. It's created in the `service.ts` file at the root of your module directory as explained in a [previous chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). +The function receives an object parameter that has a `container` property, which is the module's container that you can use to resolve resources from. In this example, you resolve the Logger utility to log a message in the terminal. -*** +Find the list of resources in the module's container in [this reference](https://docs.medusajs.com/resources/medusa-container-resources#module-container-resources/index.html.md). -## Other Directories +### 2. Export Loader in Module Definition -The following directories are optional and their content are explained more in the following chapters: +After implementing the loader, you must export it in the module's definition in the `index.ts` file at the root of the module's directory. Otherwise, the Medusa application will not run it. -- `models`: Holds the data models representing tables in the database. -- `migrations`: Holds the migration files used to reflect changes on the database. -- `loaders`: Holds the scripts to run on the Medusa application's start-up. +So, to export the loader you implemented above in the `hello` module, add the following to `src/modules/hello/index.ts`: +```ts title="src/modules/hello/index.ts" +// other imports... +import helloWorldLoader from "./loaders/hello-world" -# Service Constraints +export default Module("hello", { + // ... + loaders: [helloWorldLoader], +}) +``` -This chapter lists constraints to keep in mind when creating a service. +The second parameter of the `Module` function accepts a `loaders` property whose value is an array of loader functions. The Medusa application will execute these functions when it starts. -## Use Async Methods +### Test the Loader -Medusa wraps service method executions to inject useful context or transactions. However, since Medusa can't detect whether the method is asynchronous, it always executes methods in the wrapper with the `await` keyword. +Assuming your module is [added to Medusa's configuration](https://docs.medusajs.com/learn/fundamentals/modules#4-add-module-to-medusas-configurations/index.html.md), you can test the loader by starting the Medusa application: -For example, if you have a synchronous `getMessage` method, and you use it in other resources like workflows, Medusa executes it as an async method: +```bash npm2yarn +npm run dev +``` -```ts -await helloModuleService.getMessage() +Then, you'll find the following message logged in the terminal: + +```plain +info: [HELLO MODULE] Just started the Medusa application! ``` -So, make sure your service's methods are always async to avoid unexpected errors or behavior. +This indicates that the loader in the `hello` module ran and logged this message. -```ts highlights={[["8", "", "Method must be async."], ["13", "async", "Correct way of defining the method."]]} -import { MedusaService } from "@medusajs/framework/utils" -import MyCustom from "./models/my-custom" +*** -class HelloModuleService extends MedusaService({ - MyCustom, -}){ - // Don't - getMessage(): string { - return "Hello, World!" - } +## Example: Register Custom MongoDB Connection - // Do - async getMessage(): Promise { - return "Hello, World!" - } -} +As mentioned in this chapter's introduction, loaders are most useful when you need to register a custom resource in the module's container to re-use it in other customizations in the module. -export default HelloModuleService -``` +Consider your have a MongoDB module that allows you to perform operations on a MongoDB database. +### Prerequisites -# Module Options +- [MongoDB database that you can connect to from a local machine.](https://www.mongodb.com) +- [Install the MongoDB SDK in your Medusa application.](https://www.mongodb.com/docs/drivers/node/current/quick-start/download-and-install/#install-the-node.js-driver) -In this chapter, you’ll learn about passing options to your module from the Medusa application’s configurations and using them in the module’s resources. +To connect to the database, you create the following loader in your module: -## What are Module Options? +```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights} +import { LoaderOptions } from "@medusajs/framework/types" +import { asValue } from "awilix" +import { MongoClient } from "mongodb" -A module can receive options to customize or configure its functionality. For example, if you’re creating a module that integrates a third-party service, you’ll want to receive the integration credentials in the options rather than adding them directly in your code. +type ModuleOptions = { + connection_url?: string + db_name?: string +} -*** +export default async function mongoConnectionLoader({ + container, + options, +}: LoaderOptions) { + if (!options.connection_url) { + throw new Error(`[MONGO MDOULE]: connection_url option is required.`) + } + if (!options.db_name) { + throw new Error(`[MONGO MDOULE]: db_name option is required.`) + } + const logger = container.resolve("logger") + + try { + const clientDb = ( + await (new MongoClient(options.connection_url)).connect() + ).db(options.db_name) -## How to Pass Options to a Module? + logger.info("Connected to MongoDB") -To pass options to a module, add an `options` property to the module’s configuration in `medusa-config.ts`. + container.register( + "mongoClient", + asValue(clientDb) + ) + } catch (e) { + logger.error( + `[MONGO MDOULE]: An error occurred while connecting to MongoDB: ${e}` + ) + } +} +``` -For example: +The loader function accepts in its object parameter an `options` property, which is the options passed to the module in Medusa's configurations. For example: -```ts title="medusa-config.ts" +```ts title="medusa-config.ts" highlights={optionHighlights} module.exports = defineConfig({ // ... modules: [ { - resolve: "./src/modules/hello", + resolve: "./src/modules/mongo", options: { - capitalize: true, + connection_url: process.env.MONGO_CONNECTION_URL, + db_name: process.env.MONGO_DB_NAME, }, }, ], }) ``` -The `options` property’s value is an object. You can pass any properties you want. - -### Pass Options to a Module in a Plugin - -If your module is part of a plugin, you can pass options to the module in the plugin’s configuration. +Passing options is useful when your module needs informations like connection URLs or API keys, as it ensures your module can be re-usable across applications. For the MongoDB Module, you expect two options: -For example: +- `connection_url`: the URL to connect to the MongoDB database. +- `db_name`: The name of the database to connect to. -```ts title="medusa-config.ts" -import { defineConfig } from "@medusajs/framework/utils" -module.exports = defineConfig({ - plugins: [ - { - resolve: "@myorg/plugin-name", - options: { - capitalize: true, - }, - }, - ], -}) -``` +In the loader, you check first that these options are set before proceeding. Then, you create an instance of the MongoDB client and connect to the database specified in the options. -The `options` property in the plugin configuration is passed to all modules in a plugin. +After creating the client, you register it in the module's container using the container's `register` method. The method accepts two parameters: -*** +1. The key to register the resource under, which in this case is `mongoClient`. You'll use this name later to resolve the client. +2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [awilix package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa. -## Access Module Options in Main Service +### Use Custom Registered Resource in Module's Service -The module’s main service receives the module options as a second parameter. +After registering the custom MongoDB client in the module's container, you can now resolve and use it in the module's service. For example: -```ts title="src/modules/hello/service.ts" highlights={[["12"], ["14", "options?: ModuleOptions"], ["17"], ["18"], ["19"]]} -import { MedusaService } from "@medusajs/framework/utils" -import MyCustom from "./models/my-custom" +```ts title="src/modules/mongo/service.ts" +import type { Db } from "mongodb" -// recommended to define type in another file -type ModuleOptions = { - capitalize?: boolean +type InjectedDependencies = { + mongoClient: Db } -export default class HelloModuleService extends MedusaService({ - MyCustom, -}){ - protected options_: ModuleOptions +export default class MongoModuleService { + private mongoClient_: Db - constructor({}, options?: ModuleOptions) { - super(...arguments) + constructor({ mongoClient }: InjectedDependencies) { + this.mongoClient_ = mongoClient + } - this.options_ = options || { - capitalize: false, - } + async createMovie({ title }: { + title: string + }) { + const moviesCol = this.mongoClient_.collection("movie") + + const insertedMovie = await moviesCol.insertOne({ + title, + }) + + const movie = await moviesCol.findOne({ + _id: insertedMovie.insertedId, + }) + + return movie } - // ... + async deleteMovie(id: string) { + const moviesCol = this.mongoClient_.collection("movie") + + await moviesCol.deleteOne({ + _id: { + equals: id, + }, + }) + } } ``` -*** +The service `MongoModuleService` resolves the `mongoClient` resource you registered in the loader and sets it as a class property. You then use it in the `createMovie` and `deleteMovie` methods, which create and delete a document in a `movie` collection in the MongoDB database, respectively. -## Access Module Options in Loader +Make sure to export the loader in the module's definition in the `index.ts` file at the root directory of the module: -The object that a module’s loaders receive as a parameter has an `options` property holding the module's options. +```ts title="src/modules/mongo/index.ts" highlights={[["9"]]} +import { Module } from "@medusajs/framework/utils" +import MongoModuleService from "./service" +import mongoConnectionLoader from "./loaders/connection" -For example: +export const MONGO_MODULE = "mongo" -```ts title="src/modules/hello/loaders/hello-world.ts" highlights={[["11"], ["12", "ModuleOptions", "The type of expected module options."], ["16"]]} -import { - LoaderOptions, -} from "@medusajs/framework/types" +export default Module(MONGO_MODULE, { + service: MongoModuleService, + loaders: [mongoConnectionLoader], +}) +``` -// recommended to define type in another file -type ModuleOptions = { - capitalize?: boolean -} +### Test it Out -export default async function helloWorldLoader({ - options, -}: LoaderOptions) { - - console.log( - "[HELLO MODULE] Just started the Medusa application!", - options - ) -} +You can test the connection out by starting the Medusa application. If it's successful, you'll see the following message logged in the terminal: + +```bash +info: Connected to MongoDB ``` -*** +You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database. -## Validate Module Options -If you expect a certain option and want to throw an error if it's not provided or isn't valid, it's recommended to perform the validation in a loader. The module's service is only instantiated when it's used, whereas the loader runs the when the Medusa application starts. +# Modules Directory Structure -So, by performing the validation in the loader, you ensure you can throw an error at an early point, rather than when the module is used. +In this document, you'll learn about the expected files and directories in your module. -For example, to validate that the Hello Module received an `apiKey` option, create the loader `src/modules/loaders/validate.ts`: +![Module Directory Structure Example](https://res.cloudinary.com/dza7lstvk/image/upload/v1714379976/Medusa%20Book/modules-dir-overview_nqq7ne.jpg) -```ts title="src/modules/hello/loaders/validate.ts" -import { LoaderOptions } from "@medusajs/framework/types" -import { MedusaError } from "@medusajs/framework/utils" +## index.ts -// recommended to define type in another file -type ModuleOptions = { - apiKey?: string -} +The `index.ts` file in the root of your module's directory is the only required file. It must export the module's definition as explained in a [previous chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). -export default async function validationLoader({ - options, -}: LoaderOptions) { - if (!options.apiKey) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - "Hello Module requires an apiKey option." - ) - } -} -``` +*** -Then, export the loader in the module's definition file, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md): +## service.ts -```ts title="src/modules/hello/index.ts" -// other imports... -import validationLoader from "./loaders/validate" +A module must have a main service. It's created in the `service.ts` file at the root of your module directory as explained in a [previous chapter](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). -export default Module("hello", { - // ... - loaders: [validationLoader], -}) -``` +*** -Now, when the Medusa application starts, the loader will run, validating the module's options and throwing an error if the `apiKey` option is missing. +## Other Directories +The following directories are optional and their content are explained more in the following chapters: -# Service Factory +- `models`: Holds the data models representing tables in the database. +- `migrations`: Holds the migration files used to reflect changes on the database. +- `loaders`: Holds the scripts to run on the Medusa application's start-up. -In this chapter, you’ll learn about what the service factory is and how to use it. -## What is the Service Factory? +# Multiple Services in a Module -Medusa provides a service factory that your module’s main service can extend. +In this chapter, you'll learn how to use multiple services in a module. -The service factory generates data management methods for your data models in the database, so you don't have to implement these methods manually. +## Module's Main and Internal Services -Your service provides data-management functionalities of your data models. +A module has one main service only, which is the service exported in the module's definition. + +However, you may use other services in your module to better organize your code or split functionalities. These are called internal services that can be resolved within your module, but not in external resources. *** -## How to Extend the Service Factory? +## How to Add an Internal Service -Medusa provides the service factory as a `MedusaService` function your service extends. The function creates and returns a service class with generated data-management methods. +### 1. Create Service -For example, create the file `src/modules/hello/service.ts` with the following content: +To add an internal service, create it in the `services` directory of your module. -```ts title="src/modules/hello/service.ts" highlights={highlights} -import { MedusaService } from "@medusajs/framework/utils" -import MyCustom from "./models/my-custom" +For example, create the file `src/modules/hello/services/client.ts` with the following content: -class HelloModuleService extends MedusaService({ - MyCustom, -}){ - // TODO implement custom methods +```ts title="src/modules/hello/services/client.ts" +export class ClientService { + async getMessage(): Promise { + return "Hello, World!" + } } - -export default HelloModuleService ``` -### MedusaService Parameters - -The `MedusaService` function accepts one parameter, which is an object of data models to generate data-management methods for. - -In the example above, since the `HelloModuleService` extends `MedusaService`, it has methods to manage the `MyCustom` data model, such as `createMyCustoms`. +### 2. Export Service in Index -### Generated Methods +Next, create an `index.ts` file under the `services` directory of the module that exports your internal services. -The service factory generates methods to manage the records of each of the data models provided in the first parameter in the database. +For example, create the file `src/modules/hello/services/index.ts` with the following content: -The method's names are the operation's name, suffixed by the data model's key in the object parameter passed to `MedusaService`. +```ts title="src/modules/hello/services/index.ts" +export * from "./client" +``` -For example, the following methods are generated for the service above: +This exports the `ClientService`. -Find a complete reference of each of the methods in [this documentation](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) +### 3. Resolve Internal Service -### listMyCustoms +Internal services exported in the `services/index.ts` file of your module are now registered in the container and can be resolved in other services in the module as well as loaders. -### listMyCustoms +For example, in your main service: -This method retrieves an array of records based on filters and pagination configurations. +```ts title="src/modules/hello/service.ts" highlights={[["5"], ["13"]]} +// other imports... +import { ClientService } from "./services" -For example: +type InjectedDependencies = { + clientService: ClientService +} -```ts -const myCustoms = await helloModuleService - .listMyCustoms() +class HelloModuleService extends MedusaService({ + MyCustom, +}){ + protected clientService_: ClientService -// with filters -const myCustoms = await helloModuleService - .listMyCustoms({ - id: ["123"] - }) + constructor({ clientService }: InjectedDependencies) { + super(...arguments) + this.clientService_ = clientService + } +} ``` -### listAndCount +You can now use your internal service in your main service. -### retrieveMyCustom +*** -This method retrieves a record by its ID. +## Resolve Resources in Internal Service + +Resolve dependencies from your module's container in the constructor of your internal service. For example: ```ts -const myCustom = await helloModuleService - .retrieveMyCustom("123") +import { Logger } from "@medusajs/framework/types" + +type InjectedDependencies = { + logger: Logger +} + +export class ClientService { + protected logger_: Logger + + constructor({ logger }: InjectedDependencies) { + this.logger_ = logger + } +} ``` -### retrieveMyCustom +*** -### updateMyCustoms +## Access Module Options -This method updates and retrieves records of the data model. +Your internal service can't access the module's options. + +To retrieve the module's options, use the `configModule` registered in the module's container, which is the configurations in `medusa-config.ts`. For example: ```ts -const myCustom = await helloModuleService - .updateMyCustoms({ - id: "123", - name: "test" - }) +import { ConfigModule } from "@medusajs/framework/types" +import { HELLO_MODULE } from ".." -// update multiple -const myCustoms = await helloModuleService - .updateMyCustoms([ - { - id: "123", +export type InjectedDependencies = { + configModule: ConfigModule +} + +export class ClientService { + protected options: Record + + constructor({ configModule }: InjectedDependencies) { + const moduleDef = configModule.modules[HELLO_MODULE] + + if (typeof moduleDef !== "boolean") { + this.options = moduleDef.options + } + } +} +``` + +The `configModule` has a `modules` property that includes all registered modules. Retrieve the module's configuration using its registration key. + +If its value is not a `boolean`, set the service's options to the module configuration's `options` property. + + +# Service Constraints + +This chapter lists constraints to keep in mind when creating a service. + +## Use Async Methods + +Medusa wraps service method executions to inject useful context or transactions. However, since Medusa can't detect whether the method is asynchronous, it always executes methods in the wrapper with the `await` keyword. + +For example, if you have a synchronous `getMessage` method, and you use it in other resources like workflows, Medusa executes it as an async method: + +```ts +await helloModuleService.getMessage() +``` + +So, make sure your service's methods are always async to avoid unexpected errors or behavior. + +```ts highlights={[["8", "", "Method must be async."], ["13", "async", "Correct way of defining the method."]]} +import { MedusaService } from "@medusajs/framework/utils" +import MyCustom from "./models/my-custom" + +class HelloModuleService extends MedusaService({ + MyCustom, +}){ + // Don't + getMessage(): string { + return "Hello, World!" + } + + // Do + async getMessage(): Promise { + return "Hello, World!" + } +} + +export default HelloModuleService +``` + + +# Service Factory + +In this chapter, you’ll learn about what the service factory is and how to use it. + +## What is the Service Factory? + +Medusa provides a service factory that your module’s main service can extend. + +The service factory generates data management methods for your data models in the database, so you don't have to implement these methods manually. + +Your service provides data-management functionalities of your data models. + +*** + +## How to Extend the Service Factory? + +Medusa provides the service factory as a `MedusaService` function your service extends. The function creates and returns a service class with generated data-management methods. + +For example, create the file `src/modules/hello/service.ts` with the following content: + +```ts title="src/modules/hello/service.ts" highlights={highlights} +import { MedusaService } from "@medusajs/framework/utils" +import MyCustom from "./models/my-custom" + +class HelloModuleService extends MedusaService({ + MyCustom, +}){ + // TODO implement custom methods +} + +export default HelloModuleService +``` + +### MedusaService Parameters + +The `MedusaService` function accepts one parameter, which is an object of data models to generate data-management methods for. + +In the example above, since the `HelloModuleService` extends `MedusaService`, it has methods to manage the `MyCustom` data model, such as `createMyCustoms`. + +### Generated Methods + +The service factory generates methods to manage the records of each of the data models provided in the first parameter in the database. + +The method's names are the operation's name, suffixed by the data model's key in the object parameter passed to `MedusaService`. + +For example, the following methods are generated for the service above: + +Find a complete reference of each of the methods in [this documentation](https://docs.medusajs.com/resources/service-factory-reference/index.html.md) + +### listMyCustoms + +### listMyCustoms + +This method retrieves an array of records based on filters and pagination configurations. + +For example: + +```ts +const myCustoms = await helloModuleService + .listMyCustoms() + +// with filters +const myCustoms = await helloModuleService + .listMyCustoms({ + id: ["123"] + }) +``` + +### listAndCount + +### retrieveMyCustom + +This method retrieves a record by its ID. + +For example: + +```ts +const myCustom = await helloModuleService + .retrieveMyCustom("123") +``` + +### retrieveMyCustom + +### updateMyCustoms + +This method updates and retrieves records of the data model. + +For example: + +```ts +const myCustom = await helloModuleService + .updateMyCustoms({ + id: "123", + name: "test" + }) + +// update multiple +const myCustoms = await helloModuleService + .updateMyCustoms([ + { + id: "123", name: "test" }, { @@ -11107,473 +11486,295 @@ export default HelloModuleService ``` -# Create a Plugin +# Module Options -In this chapter, you'll learn how to create a Medusa plugin and publish it. +In this chapter, you’ll learn about passing options to your module from the Medusa application’s configurations and using them in the module’s resources. -A [plugin](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md) is a package of reusable Medusa customizations that you can install in any Medusa application. By creating and publishing a plugin, you can reuse your Medusa customizations across multiple projects or share them with the community. +## What are Module Options? -Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0). +A module can receive options to customize or configure its functionality. For example, if you’re creating a module that integrates a third-party service, you’ll want to receive the integration credentials in the options rather than adding them directly in your code. -## 1. Create a Plugin Project +*** -Plugins are created in a separate Medusa project. This makes the development and publishing of the plugin easier. Later, you'll install that plugin in your Medusa application to test it out and use it. +## How to Pass Options to a Module? -Medusa's `create-medusa-app` CLI tool provides the option to create a plugin project. Run the following command to create a new plugin project: +To pass options to a module, add an `options` property to the module’s configuration in `medusa-config.ts`. -```bash -npx create-medusa-app my-plugin --plugin +For example: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "./src/modules/hello", + options: { + capitalize: true, + }, + }, + ], +}) ``` -This will create a new Medusa plugin project in the `my-plugin` directory. +The `options` property’s value is an object. You can pass any properties you want. -### Plugin Directory Structure +### Pass Options to a Module in a Plugin -After the installation is done, the plugin structure will look like this: +If your module is part of a plugin, you can pass options to the module in the plugin’s configuration. -![Directory structure of a plugin project](https://res.cloudinary.com/dza7lstvk/image/upload/v1737019441/Medusa%20Book/project-dir_q4xtri.jpg) +For example: -- `src/`: Contains the Medusa customizations. -- `src/admin`: Contains [admin extensions](https://docs.medusajs.com/learn/fundamentals/admin/index.html.md). -- `src/api`: Contains [API routes](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) and [middlewares](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md). You can add store, admin, or any custom API routes. -- `src/jobs`: Contains [scheduled jobs](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md). -- `src/links`: Contains [module links](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md). -- `src/modules`: Contains [modules](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md). -- `src/provider`: Contains [module providers](#create-module-providers). -- `src/subscribers`: Contains [subscribers](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md). -- `src/workflows`: Contains [workflows](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md). You can also add [hooks](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md) under `src/workflows/hooks`. -- `package.json`: Contains the plugin's package information, including general information and dependencies. -- `tsconfig.json`: Contains the TypeScript configuration for the plugin. +```ts title="medusa-config.ts" +import { defineConfig } from "@medusajs/framework/utils" +module.exports = defineConfig({ + plugins: [ + { + resolve: "@myorg/plugin-name", + options: { + capitalize: true, + }, + }, + ], +}) +``` + +The `options` property in the plugin configuration is passed to all modules in a plugin. *** -## 2. Prepare Plugin +## Access Module Options in Main Service -Before developing, testing, and publishing your plugin, make sure its name in `package.json` is correct. This is the name you'll use to install the plugin in your Medusa application. +The module’s main service receives the module options as a second parameter. For example: -```json title="package.json" -{ - "name": "@myorg/plugin-name", +```ts title="src/modules/hello/service.ts" highlights={[["12"], ["14", "options?: ModuleOptions"], ["17"], ["18"], ["19"]]} +import { MedusaService } from "@medusajs/framework/utils" +import MyCustom from "./models/my-custom" + +// recommended to define type in another file +type ModuleOptions = { + capitalize?: boolean +} + +export default class HelloModuleService extends MedusaService({ + MyCustom, +}){ + protected options_: ModuleOptions + + constructor({}, options?: ModuleOptions) { + super(...arguments) + + this.options_ = options || { + capitalize: false, + } + } + // ... } ``` -In addition, make sure that the `keywords` field in `package.json` includes the keyword `medusa-plugin` and `medusa-v2`. This helps Medusa list community plugins on the Medusa website: +*** -```json title="package.json" -{ - "keywords": [ - "medusa-plugin", - "medusa-v2" - ], - // ... +## Access Module Options in Loader + +The object that a module’s loaders receive as a parameter has an `options` property holding the module's options. + +For example: + +```ts title="src/modules/hello/loaders/hello-world.ts" highlights={[["11"], ["12", "ModuleOptions", "The type of expected module options."], ["16"]]} +import { + LoaderOptions, +} from "@medusajs/framework/types" + +// recommended to define type in another file +type ModuleOptions = { + capitalize?: boolean +} + +export default async function helloWorldLoader({ + options, +}: LoaderOptions) { + + console.log( + "[HELLO MODULE] Just started the Medusa application!", + options + ) } ``` *** -## 3. Publish Plugin Locally for Development and Testing - -Medusa's CLI tool provides commands to simplify developing and testing your plugin in a local Medusa application. You start by publishing your plugin in the local package registry, then install it in your Medusa application. You can then watch for changes in the plugin as you develop it. +## Validate Module Options -### Publish and Install Local Package +If you expect a certain option and want to throw an error if it's not provided or isn't valid, it's recommended to perform the validation in a loader. The module's service is only instantiated when it's used, whereas the loader runs the when the Medusa application starts. -### Prerequisites +So, by performing the validation in the loader, you ensure you can throw an error at an early point, rather than when the module is used. -- [Medusa application installed.](https://docs.medusajs.com/learn/installation/index.html.md) +For example, to validate that the Hello Module received an `apiKey` option, create the loader `src/modules/loaders/validate.ts`: -The first time you create your plugin, you need to publish the package into a local package registry, then install it in your Medusa application. This is a one-time only process. +```ts title="src/modules/hello/loaders/validate.ts" +import { LoaderOptions } from "@medusajs/framework/types" +import { MedusaError } from "@medusajs/framework/utils" -To publish the plugin to the local registry, run the following command in your plugin project: +// recommended to define type in another file +type ModuleOptions = { + apiKey?: string +} -```bash title="Plugin project" -npx medusa plugin:publish +export default async function validationLoader({ + options, +}: LoaderOptions) { + if (!options.apiKey) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Hello Module requires an apiKey option." + ) + } +} ``` -This command uses [Yalc](https://github.com/wclr/yalc) under the hood to publish the plugin to a local package registry. The plugin is published locally under the name you specified in `package.json`. +Then, export the loader in the module's definition file, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/loaders/index.html.md): -Next, navigate to your Medusa application: +```ts title="src/modules/hello/index.ts" +// other imports... +import validationLoader from "./loaders/validate" -```bash title="Medusa application" -cd ~/path/to/medusa-app +export default Module("hello", { + // ... + loaders: [validationLoader], +}) ``` -Make sure to replace `~/path/to/medusa-app` with the path to your Medusa application. - -Then, if your project was created before v2.3.1 of Medusa, make sure to install `yalc` as a development dependency: +Now, when the Medusa application starts, the loader will run, validating the module's options and throwing an error if the `apiKey` option is missing. -```bash npm2yarn title="Medusa application" -npm install --save-dev yalc -``` -After that, run the following Medusa CLI command to install the plugin: +# Scheduled Jobs Number of Executions -```bash title="Medusa application" -npx medusa plugin:add @myorg/plugin-name -``` +In this chapter, you'll learn how to set a limit on the number of times a scheduled job is executed. -Make sure to replace `@myorg/plugin-name` with the name of your plugin as specified in `package.json`. Your plugin will be installed from the local package registry into your Medusa application. +## numberOfExecutions Option -### Register Plugin in Medusa Application +The export configuration object of the scheduled job accepts an optional property `numberOfExecutions`. Its value is a number indicating how many times the scheduled job can be executed during the Medusa application's runtime. -After installing the plugin, you need to register it in your Medusa application in the configurations defined in `medusa-config.ts`. +For example: -Add the plugin to the `plugins` array in the `medusa-config.ts` file: +```ts highlights={highlights} +export default async function myCustomJob() { + console.log("I'll be executed three times only.") +} -```ts title="medusa-config.ts" highlights={pluginHighlights} -module.exports = defineConfig({ - // ... - plugins: [ - { - resolve: "@myorg/plugin-name", - options: {}, - }, - ], -}) +export const config = { + name: "hello-world", + // execute every minute + schedule: "* * * * *", + numberOfExecutions: 3, +} ``` -The `plugins` configuration is an array of objects where each object has a `resolve` key whose value is the name of the plugin package. +The above scheduled job has the `numberOfExecutions` configuration set to `3`. -#### Pass Module Options through Plugin +So, it'll only execute 3 times, each every minute, then it won't be executed anymore. -Each plugin configuration also accepts an `options` property, whose value is an object of options to pass to the plugin's modules. +If you restart the Medusa application, the scheduled job will be executed again until reaching the number of executions specified. -For example: -```ts title="medusa-config.ts" highlights={pluginOptionsHighlight} -module.exports = defineConfig({ - // ... - plugins: [ - { - resolve: "@myorg/plugin-name", - options: { - apiKey: true, - }, - }, - ], -}) -``` +# Access Workflow Errors -The `options` property in the plugin configuration is passed to all modules in the plugin. Learn more about module options in [this chapter](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md). +In this chapter, you’ll learn how to access errors that occur during a workflow’s execution. -### Watch Plugin Changes During Development +## How to Access Workflow Errors? -While developing your plugin, you can watch for changes in the plugin and automatically update the plugin in the Medusa application using it. This is the only command you'll continuously need during your plugin development. +By default, when an error occurs in a workflow, it throws that error, and the execution stops. -To do that, run the following command in your plugin project: +You can configure the workflow to return the errors instead so that you can access and handle them differently. -```bash title="Plugin project" -npx medusa plugin:develop -``` +For example: -This command will: +```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import type { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import myWorkflow from "../../../workflows/hello-world" -- Watch for changes in the plugin. Whenever a file is changed, the plugin is automatically built. -- Publish the plugin changes to the local package registry. This will automatically update the plugin in the Medusa application using it. You can also benefit from real-time HMR updates of admin extensions. +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const { result, errors } = await myWorkflow(req.scope) + .run({ + // ... + throwOnError: false, + }) -### Start Medusa Application + if (errors.length) { + return res.send({ + errors: errors.map((error) => error.error), + }) + } -You can start your Medusa application's development server to test out your plugin: + res.send(result) +} -```bash npm2yarn title="Medusa application" -npm run dev ``` -While your Medusa application is running and the plugin is being watched, you can test your plugin while developing it in the Medusa application. +The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of `run`'s output. -*** +The value of `errors` is an array of error objects. Each object has an `error` property, whose value is the name or text of the thrown error. -## 4. Create Customizations in the Plugin -You can now build your plugin's customizations. The following guide explains how to build different customizations in your plugin. +# Expose a Workflow Hook -- [Create a module](https://docs.medusajs.com/learn/fundamentals/modules/index.html.md) -- [Create a module link](https://docs.medusajs.com/learn/fundamentals/module-links/index.html.md) -- [Create a workflow](https://docs.medusajs.com/learn/fundamentals/workflows/index.html.md) -- [Add a workflow hook](https://docs.medusajs.com/learn/fundamentals/workflows/add-workflow-hook/index.html.md) -- [Create an API route](https://docs.medusajs.com/learn/fundamentals/api-routes/index.html.md) -- [Add a subscriber](https://docs.medusajs.com/learn/fundamentals/events-and-subscribers/index.html.md) -- [Add a scheduled job](https://docs.medusajs.com/learn/fundamentals/scheduled-jobs/index.html.md) -- [Add an admin widget](https://docs.medusajs.com/learn/fundamentals/admin/widgets/index.html.md) -- [Add an admin UI route](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes/index.html.md) +In this chapter, you'll learn how to expose a hook in your workflow. -While building those customizations, you can test them in your Medusa application by [watching the plugin changes](#watch-plugin-changes-during-development) and [starting the Medusa application](#start-medusa-application). +## When to Expose a Hook -### Generating Migrations for Modules +Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow. -During your development, you may need to generate migrations for modules in your plugin. To do that, use the `plugin:db:generate` command: +Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead. -```bash title="Plugin project" -npx medusa plugin:db:generate -``` +*** -This command generates migrations for all modules in the plugin. You can then run these migrations on the Medusa application that the plugin is installed in using the `db:migrate` command: +## How to Expose a Hook in a Workflow? -```bash title="Medusa application" -npx medusa db:migrate -``` +To expose a hook in your workflow, use `createHook` from the Workflows SDK. -### Importing Module Resources +For example: -Your plugin project should have the following exports in `package.json`: +```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights} +import { + createStep, + createHook, + createWorkflow, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" +import { createProductStep } from "./steps/create-product" -```json title="package.json" -{ - "exports": { - "./package.json": "./package.json", - "./workflows": "./.medusa/server/src/workflows/index.js", - "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js", - "./providers/*": "./.medusa/server/src/providers/*/index.js", - "./*": "./.medusa/server/src/*.js" +export const myWorkflow = createWorkflow( + "my-workflow", + function (input) { + const product = createProductStep(input) + const productCreatedHook = createHook( + "productCreated", + { productId: product.id } + ) + + return new WorkflowResponse(product, { + hooks: [productCreatedHook], + }) } -} +) ``` -Aside from the `./package.json` and `./providers`, these exports are only a recommendation. You can cherry-pick the files and directories you want to export. +The `createHook` function accepts two parameters: -The plugin exports the following files and directories: +1. The first is a string indicating the hook's name. You use this to consume the hook later. +2. The second is the input to pass to the hook handler. -- `./package.json`: The package.json file. Medusa needs to access the `package.json` when registering the plugin. -- `./workflows`: The workflows exported in `./src/workflows/index.ts`. -- `./.medusa/server/src/modules/*`: The definition file of modules. This is useful if you create links to the plugin's modules in the Medusa application. -- `./providers/*`: The definition file of module providers. This allows you to register the plugin's providers in the Medusa application. -- `./*`: Any other files in the plugin's `src` directory. +The workflow must also pass an object having a `hooks` property as a second parameter to the `WorkflowResponse` constructor. Its value is an array of the workflow's hooks. -With these exports, you can import the plugin's resources in the Medusa application's code like this: - -`@myorg/plugin-name` is the plugin package's name. - -```ts -import { Workflow1, Workflow2 } from "@myorg/plugin-name/workflows" -import BlogModule from "@myorg/plugin-name/modules/blog" -// import other files created in plugin like ./src/types/blog.ts -import BlogType from "@myorg/plugin-name/types/blog" -``` - -And you can register a module provider in the Medusa application's `medusa-config.ts` like this: - -```ts highlights={[["9"]]} title="medusa-config.ts" -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/medusa/notification", - options: { - providers: [ - { - resolve: "@myorg/plugin-name/providers/my-notification", - id: "my-notification", - options: { - channels: ["email"], - // provider options... - }, - }, - ], - }, - }, - ], -}) -``` - -You pass to `resolve` the path to the provider relative to the plugin package. So, in this example, the `my-notification` provider is located in `./src/providers/my-notification/index.ts` of the plugin. - -### Create Module Providers - -To learn how to create module providers, refer to the following guides: - -- [File Module Provider](https://docs.medusajs.com/resources/references/file-provider-module/index.html.md) -- [Notification Module Provider](https://docs.medusajs.com/resources/references/notification-provider-module/index.html.md) -- [Auth Module Provider](https://docs.medusajs.com/resources/references/auth/provider/index.html.md) -- [Payment Module Provider](https://docs.medusajs.com/resources/references/payment/provider/index.html.md) -- [Fulfillment Module Provider](https://docs.medusajs.com/resources/references/fulfillment/provider/index.html.md) -- [Tax Module Provider](https://docs.medusajs.com/resources/references/tax/provider/index.html.md) - -*** - -## 5. Publish Plugin to NPM - -Medusa's CLI tool provides a command that bundles your plugin to be published to npm. Once you're ready to publish your plugin publicly, run the following command in your plugin project: - -```bash -npx medusa plugin:build -``` - -The command will compile an output in the `.medusa/server` directory. - -You can now publish the plugin to npm using the [NPM CLI tool](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Run the following command to publish the plugin to npm: - -```bash -npm publish -``` - -If you haven't logged in before with your NPM account, you'll be asked to log in first. Then, your package is published publicly to be used in any Medusa application. - -### Install Public Plugin in Medusa Application - -You install a plugin that's published publicly using your package manager. For example: - -```bash npm2yarn -npm install @myorg/plugin-name -``` - -Where `@myorg/plugin-name` is the name of your plugin as published on NPM. - -Then, register the plugin in your Medusa application's configurations as explained in [this section](#register-plugin-in-medusa-application). - -*** - -## Update a Published Plugin - -To update the Medusa dependencies in a plugin, refer to [this documentation](https://docs.medusajs.com/learn/update#update-plugin-project/index.html.md). - -If you've published a plugin and you've made changes to it, you'll have to publish the update to NPM again. - -First, run the following command to change the version of the plugin: - -```bash -npm version -``` - -Where `` indicates the type of version update you’re publishing. For example, it can be `major` or `minor`. Refer to the [npm version documentation](https://docs.npmjs.com/cli/v10/commands/npm-version) for more information. - -Then, re-run the same commands for publishing a plugin: - -```bash -npx medusa plugin:build -npm publish -``` - -This will publish an updated version of your plugin under a new version. - - -# Access Workflow Errors - -In this chapter, you’ll learn how to access errors that occur during a workflow’s execution. - -## How to Access Workflow Errors? - -By default, when an error occurs in a workflow, it throws that error, and the execution stops. - -You can configure the workflow to return the errors instead so that you can access and handle them differently. - -For example: - -```ts title="src/api/workflows/route.ts" highlights={highlights} collapsibleLines="1-6" expandButtonLabel="Show Imports" -import type { - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import myWorkflow from "../../../workflows/hello-world" - -export async function GET( - req: MedusaRequest, - res: MedusaResponse -) { - const { result, errors } = await myWorkflow(req.scope) - .run({ - // ... - throwOnError: false, - }) - - if (errors.length) { - return res.send({ - errors: errors.map((error) => error.error), - }) - } - - res.send(result) -} - -``` - -The object passed to the `run` method accepts a `throwOnError` property. When disabled, the errors are returned in the `errors` property of `run`'s output. - -The value of `errors` is an array of error objects. Each object has an `error` property, whose value is the name or text of the thrown error. - - -# Scheduled Jobs Number of Executions - -In this chapter, you'll learn how to set a limit on the number of times a scheduled job is executed. - -## numberOfExecutions Option - -The export configuration object of the scheduled job accepts an optional property `numberOfExecutions`. Its value is a number indicating how many times the scheduled job can be executed during the Medusa application's runtime. - -For example: - -```ts highlights={highlights} -export default async function myCustomJob() { - console.log("I'll be executed three times only.") -} - -export const config = { - name: "hello-world", - // execute every minute - schedule: "* * * * *", - numberOfExecutions: 3, -} -``` - -The above scheduled job has the `numberOfExecutions` configuration set to `3`. - -So, it'll only execute 3 times, each every minute, then it won't be executed anymore. - -If you restart the Medusa application, the scheduled job will be executed again until reaching the number of executions specified. - - -# Expose a Workflow Hook - -In this chapter, you'll learn how to expose a hook in your workflow. - -## When to Expose a Hook - -Your workflow is reusable in other applications, and you allow performing an external action at some point in your workflow. - -Your workflow isn't reusable by other applications. Use a step that performs what a hook handler would instead. - -*** - -## How to Expose a Hook in a Workflow? - -To expose a hook in your workflow, use `createHook` from the Workflows SDK. - -For example: - -```ts title="src/workflows/my-workflow/index.ts" highlights={hookHighlights} -import { - createStep, - createHook, - createWorkflow, - WorkflowResponse, -} from "@medusajs/framework/workflows-sdk" -import { createProductStep } from "./steps/create-product" - -export const myWorkflow = createWorkflow( - "my-workflow", - function (input) { - const product = createProductStep(input) - const productCreatedHook = createHook( - "productCreated", - { productId: product.id } - ) - - return new WorkflowResponse(product, { - hooks: [productCreatedHook], - }) - } -) -``` - -The `createHook` function accepts two parameters: - -1. The first is a string indicating the hook's name. You use this to consume the hook later. -2. The second is the input to pass to the hook handler. - -The workflow must also pass an object having a `hooks` property as a second parameter to the `WorkflowResponse` constructor. Its value is an array of the workflow's hooks. - -### How to Consume the Hook? +### How to Consume the Hook? To consume the hook of the workflow, create the file `src/workflows/hooks/my-workflow.ts` with the following content: @@ -12353,221 +12554,17 @@ const step1 = createStep( ``` -# Execute Another Workflow +# Long-Running Workflows -In this chapter, you'll learn how to execute a workflow in another. +In this chapter, you’ll learn what a long-running workflow is and how to configure it. -## Execute in a Workflow +## What is a Long-Running Workflow? -To execute a workflow in another, use the `runAsStep` method that every workflow has. +When you execute a workflow, you wait until the workflow finishes execution to receive the output. -For example: +A long-running workflow is a workflow that continues its execution in the background. You don’t receive its output immediately. Instead, you subscribe to the workflow execution to listen to status changes and receive its result once the execution is finished. -```ts highlights={workflowsHighlights} collapsibleLines="1-7" expandMoreButton="Show Imports" -import { - createWorkflow, -} from "@medusajs/framework/workflows-sdk" -import { - createProductsWorkflow, -} from "@medusajs/medusa/core-flows" - -const workflow = createWorkflow( - "hello-world", - async (input) => { - const products = createProductsWorkflow.runAsStep({ - input: { - products: [ - // ... - ], - }, - }) - - // ... - } -) -``` - -Instead of invoking the workflow and passing it the container, you use its `runAsStep` method and pass it an object as a parameter. - -The object has an `input` property to pass input to the workflow. - -*** - -## Preparing Input Data - -If you need to perform some data manipulation to prepare the other workflow's input data, use `transform` from the Workflows SDK. - -Learn about transform in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md). - -For example: - -```ts highlights={transformHighlights} collapsibleLines="1-12" -import { - createWorkflow, - transform, -} from "@medusajs/framework/workflows-sdk" -import { - createProductsWorkflow, -} from "@medusajs/medusa/core-flows" - -type WorkflowInput = { - title: string -} - -const workflow = createWorkflow( - "hello-product", - async (input: WorkflowInput) => { - const createProductsData = transform({ - input, - }, (data) => [ - { - title: `Hello ${data.input.title}`, - }, - ]) - - const products = createProductsWorkflow.runAsStep({ - input: { - products: createProductsData, - }, - }) - - // ... - } -) -``` - -In this example, you use the `transform` function to prepend `Hello` to the title of the product. Then, you pass the result as an input to the `createProductsWorkflow`. - -*** - -## Run Workflow Conditionally - -To run a workflow in another based on a condition, use when-then from the Workflows SDK. - -Learn about when-then in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md). - -For example: - -```ts highlights={whenHighlights} collapsibleLines="1-16" -import { - createWorkflow, - when, -} from "@medusajs/framework/workflows-sdk" -import { - createProductsWorkflow, -} from "@medusajs/medusa/core-flows" -import { - CreateProductWorkflowInputDTO, -} from "@medusajs/framework/types" - -type WorkflowInput = { - product?: CreateProductWorkflowInputDTO - should_create?: boolean -} - -const workflow = createWorkflow( - "hello-product", - async (input: WorkflowInput) => { - const product = when(input, ({ should_create }) => should_create) - .then(() => { - return createProductsWorkflow.runAsStep({ - input: { - products: [input.product], - }, - }) - }) - } -) -``` - -In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled. - - -# Multiple Step Usage in Workflow - -In this chapter, you'll learn how to use a step multiple times in a workflow. - -## Problem Reusing a Step in a Workflow - -In some cases, you may need to use a step multiple times in the same workflow. - -The most common example is using the `useQueryGraphStep` multiple times in a workflow to retrieve multiple unrelated data, such as customers and products. - -Each workflow step must have a unique ID, which is the ID passed as a first parameter when creating the step: - -```ts -const useQueryGraphStep = createStep( - "use-query-graph" - // ... -) -``` - -This causes an error when you use the same step multiple times in a workflow, as it's registered in the workflow as two steps having the same ID: - -```ts -const helloWorkflow = createWorkflow( - "hello", - () => { - const { data: products } = useQueryGraphStep({ - entity: "product", - fields: ["id"], - }) - - // ERROR OCCURS HERE: A STEP HAS THE SAME ID AS ANOTHER IN THE WORKFLOW - const { data: customers } = useQueryGraphStep({ - entity: "customer", - fields: ["id"], - }) - } -) -``` - -The next section explains how to fix this issue to use the same step multiple times in a workflow. - -*** - -## How to Use a Step Multiple Times in a Workflow? - -When you execute a step in a workflow, you can chain a `config` method to it to change the step's config. - -Use the `config` method to change a step's ID for a single execution. - -So, this is the correct way to write the example above: - -```ts highlights={highlights} -const helloWorkflow = createWorkflow( - "hello", - () => { - const { data: products } = useQueryGraphStep({ - entity: "product", - fields: ["id"], - }) - - // ✓ No error occurs, the step has a different ID. - const { data: customers } = useQueryGraphStep({ - entity: "customer", - fields: ["id"], - }).config({ name: "fetch-customers" }) - } -) -``` - -The `config` method accepts an object with a `name` property. Its value is a new ID of the step to use for this execution only. - -The first `useQueryGraphStep` usage has the ID `use-query-graph`, and the second `useQueryGraphStep` usage has the ID `fetch-customers`. - - -# Long-Running Workflows - -In this chapter, you’ll learn what a long-running workflow is and how to configure it. - -## What is a Long-Running Workflow? - -When you execute a workflow, you wait until the workflow finishes execution to receive the output. - -A long-running workflow is a workflow that continues its execution in the background. You don’t receive its output immediately. Instead, you subscribe to the workflow execution to listen to status changes and receive its result once the execution is finished. - -### Why use Long-Running Workflows? +### Why use Long-Running Workflows? Long-running workflows are useful if: @@ -12845,56 +12842,260 @@ To find a full example of a long-running workflow, refer to the [restaurant-deli In the recipe, you use a long-running workflow that moves an order from placed to completed. The workflow waits for the restaurant to accept the order, the driver to pick up the order, and other external actions. -# Run Workflow Steps in Parallel - -In this chapter, you’ll learn how to run workflow steps in parallel. - -## parallelize Utility Function +# Multiple Step Usage in Workflow -If your workflow has steps that don’t rely on one another’s results, run them in parallel using `parallelize` from the Workflows SDK. +In this chapter, you'll learn how to use a step multiple times in a workflow. -The workflow waits until all steps passed to the `parallelize` function finish executing before continuing to the next step. +## Problem Reusing a Step in a Workflow -For example: +In some cases, you may need to use a step multiple times in the same workflow. -```ts highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports" -import { - createWorkflow, - WorkflowResponse, - parallelize, -} from "@medusajs/framework/workflows-sdk" -import { - createProductStep, - getProductStep, - createPricesStep, - attachProductToSalesChannelStep, -} from "./steps" +The most common example is using the `useQueryGraphStep` multiple times in a workflow to retrieve multiple unrelated data, such as customers and products. -interface WorkflowInput { - title: string -} +Each workflow step must have a unique ID, which is the ID passed as a first parameter when creating the step: -const myWorkflow = createWorkflow( - "my-workflow", - (input: WorkflowInput) => { - const product = createProductStep(input) +```ts +const useQueryGraphStep = createStep( + "use-query-graph" + // ... +) +``` - const [prices, productSalesChannel] = parallelize( - createPricesStep(product), - attachProductToSalesChannelStep(product) - ) +This causes an error when you use the same step multiple times in a workflow, as it's registered in the workflow as two steps having the same ID: - const id = product.id - const refetchedProduct = getProductStep(product.id) +```ts +const helloWorkflow = createWorkflow( + "hello", + () => { + const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id"], + }) - return new WorkflowResponse(refetchedProduct) - } + // ERROR OCCURS HERE: A STEP HAS THE SAME ID AS ANOTHER IN THE WORKFLOW + const { data: customers } = useQueryGraphStep({ + entity: "customer", + fields: ["id"], + }) + } ) ``` -The `parallelize` function accepts the steps to run in parallel as a parameter. +The next section explains how to fix this issue to use the same step multiple times in a workflow. -It returns an array of the steps' results in the same order they're passed to the `parallelize` function. +*** + +## How to Use a Step Multiple Times in a Workflow? + +When you execute a step in a workflow, you can chain a `config` method to it to change the step's config. + +Use the `config` method to change a step's ID for a single execution. + +So, this is the correct way to write the example above: + +```ts highlights={highlights} +const helloWorkflow = createWorkflow( + "hello", + () => { + const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id"], + }) + + // ✓ No error occurs, the step has a different ID. + const { data: customers } = useQueryGraphStep({ + entity: "customer", + fields: ["id"], + }).config({ name: "fetch-customers" }) + } +) +``` + +The `config` method accepts an object with a `name` property. Its value is a new ID of the step to use for this execution only. + +The first `useQueryGraphStep` usage has the ID `use-query-graph`, and the second `useQueryGraphStep` usage has the ID `fetch-customers`. + + +# Execute Another Workflow + +In this chapter, you'll learn how to execute a workflow in another. + +## Execute in a Workflow + +To execute a workflow in another, use the `runAsStep` method that every workflow has. + +For example: + +```ts highlights={workflowsHighlights} collapsibleLines="1-7" expandMoreButton="Show Imports" +import { + createWorkflow, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" + +const workflow = createWorkflow( + "hello-world", + async (input) => { + const products = createProductsWorkflow.runAsStep({ + input: { + products: [ + // ... + ], + }, + }) + + // ... + } +) +``` + +Instead of invoking the workflow and passing it the container, you use its `runAsStep` method and pass it an object as a parameter. + +The object has an `input` property to pass input to the workflow. + +*** + +## Preparing Input Data + +If you need to perform some data manipulation to prepare the other workflow's input data, use `transform` from the Workflows SDK. + +Learn about transform in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/variable-manipulation/index.html.md). + +For example: + +```ts highlights={transformHighlights} collapsibleLines="1-12" +import { + createWorkflow, + transform, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" + +type WorkflowInput = { + title: string +} + +const workflow = createWorkflow( + "hello-product", + async (input: WorkflowInput) => { + const createProductsData = transform({ + input, + }, (data) => [ + { + title: `Hello ${data.input.title}`, + }, + ]) + + const products = createProductsWorkflow.runAsStep({ + input: { + products: createProductsData, + }, + }) + + // ... + } +) +``` + +In this example, you use the `transform` function to prepend `Hello` to the title of the product. Then, you pass the result as an input to the `createProductsWorkflow`. + +*** + +## Run Workflow Conditionally + +To run a workflow in another based on a condition, use when-then from the Workflows SDK. + +Learn about when-then in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md). + +For example: + +```ts highlights={whenHighlights} collapsibleLines="1-16" +import { + createWorkflow, + when, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" +import { + CreateProductWorkflowInputDTO, +} from "@medusajs/framework/types" + +type WorkflowInput = { + product?: CreateProductWorkflowInputDTO + should_create?: boolean +} + +const workflow = createWorkflow( + "hello-product", + async (input: WorkflowInput) => { + const product = when(input, ({ should_create }) => should_create) + .then(() => { + return createProductsWorkflow.runAsStep({ + input: { + products: [input.product], + }, + }) + }) + } +) +``` + +In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled. + + +# Run Workflow Steps in Parallel + +In this chapter, you’ll learn how to run workflow steps in parallel. + +## parallelize Utility Function + +If your workflow has steps that don’t rely on one another’s results, run them in parallel using `parallelize` from the Workflows SDK. + +The workflow waits until all steps passed to the `parallelize` function finish executing before continuing to the next step. + +For example: + +```ts highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports" +import { + createWorkflow, + WorkflowResponse, + parallelize, +} from "@medusajs/framework/workflows-sdk" +import { + createProductStep, + getProductStep, + createPricesStep, + attachProductToSalesChannelStep, +} from "./steps" + +interface WorkflowInput { + title: string +} + +const myWorkflow = createWorkflow( + "my-workflow", + (input: WorkflowInput) => { + const product = createProductStep(input) + + const [prices, productSalesChannel] = parallelize( + createPricesStep(product), + attachProductToSalesChannelStep(product) + ) + + const id = product.id + const refetchedProduct = getProductStep(product.id) + + return new WorkflowResponse(refetchedProduct) + } +) +``` + +The `parallelize` function accepts the steps to run in parallel as a parameter. + +It returns an array of the steps' results in the same order they're passed to the `parallelize` function. So, `prices` is the result of `createPricesStep`, and `productSalesChannel` is the result of `attachProductToSalesChannelStep`. @@ -13044,226 +13245,21 @@ if (workflowExecution.state === "failed") { Other state values include `done`, `invoking`, and `compensating`. -# Variable Manipulation in Workflows with transform - -In this chapter, you'll learn how to use `transform` from the Workflows SDK to manipulate variables in a workflow. - -## Why Variable Manipulation isn't Allowed in Workflows - -Medusa creates an internal representation of the workflow definition you pass to `createWorkflow` to track and store its steps. - -At that point, variables in the workflow don't have any values. They only do when you execute the workflow. - -So, you can only pass variables as parameters to steps. But, in a workflow, you can't change a variable's value or, if the variable is an array, loop over its items. - -Instead, use `transform` from the Workflows SDK. - -Restrictions for variable manipulation is only applicable in a workflow's definition. You can still manipulate variables in your step's code. - -*** +# Retry Failed Steps -## What is the transform Utility? +In this chapter, you’ll learn how to configure steps to allow retrial on failure. -`transform` creates a new variable as the result of manipulating other variables. +## Configure a Step’s Retrial -For example, consider you have two strings as the output of two steps: +By default, when an error occurs in a step, the step and the workflow fail, and the execution stops. -```ts -const str1 = step1() -const str2 = step2() -``` +You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter. -To concatenate the strings, you create a new variable `str3` using the `transform` function: +For example: -```ts highlights={highlights} +```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" import { - createWorkflow, - WorkflowResponse, - transform, -} from "@medusajs/framework/workflows-sdk" -// step imports... - -const myWorkflow = createWorkflow( - "hello-world", - function (input) { - const str1 = step1(input) - const str2 = step2(input) - - const str3 = transform( - { str1, str2 }, - (data) => `${data.str1}${data.str2}` - ) - - return new WorkflowResponse(str3) - } -) -``` - -`transform` accepts two parameters: - -1. The first parameter is an object of variables to manipulate. The object is passed as a parameter to `transform`'s second parameter function. -2. The second parameter is the function performing the variable manipulation. - -The value returned by the second parameter function is returned by `transform`. So, the `str3` variable holds the concatenated string. - -You can use the returned value in the rest of the workflow, either to pass it as an input to other steps or to return it in the workflow's response. - -*** - -## Example: Looping Over Array - -Use `transform` to loop over arrays to create another variable from the array's items. - -For example: - -```ts collapsibleLines="1-7" expandButtonLabel="Show Imports" -import { - createWorkflow, - WorkflowResponse, - transform, -} from "@medusajs/framework/workflows-sdk" -// step imports... - -type WorkflowInput = { - items: { - id: string - name: string - }[] -} - -const myWorkflow = createWorkflow( - "hello-world", - function ({ items }: WorkflowInput) { - const ids = transform( - { items }, - (data) => data.items.map((item) => item.id) - ) - - doSomethingStep(ids) - - // ... - } -) -``` - -This workflow receives an `items` array in its input. - -You use `transform` to create an `ids` variable, which is an array of strings holding the `id` of each item in the `items` array. - -You then pass the `ids` variable as a parameter to the `doSomethingStep`. - -*** - -## Example: Creating a Date - -If you create a date with `new Date()` in a workflow's constructor function, Medusa evaluates the date's value when it creates the internal representation of the workflow, not when the workflow is executed. - -So, use `transform` instead to create a date variable with `new Date()`. - -For example: - -```ts -const myWorkflow = createWorkflow( - "hello-world", - () => { - const today = transform({}, () => new Date()) - - doSomethingStep(today) - } -) -``` - -In this workflow, `today` is only evaluated when the workflow is executed. - -*** - -## Caveats - -### Transform Evaluation - -`transform`'s value is only evaluated if you pass its output to a step or in the workflow response. - -For example, if you have the following workflow: - -```ts -const myWorkflow = createWorkflow( - "hello-world", - function (input) { - const str = transform( - { input }, - (data) => `${data.input.str1}${data.input.str2}` - ) - - return new WorkflowResponse("done") - } -) -``` - -Since `str`'s value isn't used as a step's input or passed to `WorkflowResponse`, its value is never evaluated. - -### Data Validation - -`transform` should only be used to perform variable or data manipulation. - -If you want to perform some validation on the data, use a step or [when-then](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md) instead. - -For example: - -```ts -// DON'T -const myWorkflow = createWorkflow( - "hello-world", - function (input) { - const str = transform( - { input }, - (data) => { - if (!input.str1) { - throw new Error("Not allowed!") - } - } - ) - } -) - -// DO -const validateHasStr1Step = createStep( - "validate-has-str1", - ({ input }) => { - if (!input.str1) { - throw new Error("Not allowed!") - } - } -) - -const myWorkflow = createWorkflow( - "hello-world", - function (input) { - validateHasStr1({ - input, - }) - - // workflow continues its execution only if - // the step doesn't throw the error. - } -) -``` - - -# Retry Failed Steps - -In this chapter, you’ll learn how to configure steps to allow retrial on failure. - -## Configure a Step’s Retrial - -By default, when an error occurs in a step, the step and the workflow fail, and the execution stops. - -You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter. - -For example: - -```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" -import { - createStep, + createStep, createWorkflow, WorkflowResponse, } from "@medusajs/framework/workflows-sdk" @@ -13459,261 +13455,367 @@ export async function POST(req: MedusaRequest, res: MedusaResponse) { Your hook handler then receives that passed data in the `additional_data` object. -# Workflow Timeout +# Variable Manipulation in Workflows with transform -In this chapter, you’ll learn how to set a timeout for workflows and steps. +In this chapter, you'll learn how to use `transform` from the Workflows SDK to manipulate variables in a workflow. -## What is a Workflow Timeout? +## Why Variable Manipulation isn't Allowed in Workflows -By default, a workflow doesn’t have a timeout. It continues execution until it’s finished or an error occurs. +Medusa creates an internal representation of the workflow definition you pass to `createWorkflow` to track and store its steps. -You can configure a workflow’s timeout to indicate how long the workflow can execute. If a workflow's execution time passes the configured timeout, it is failed and an error is thrown. +At that point, variables in the workflow don't have any values. They only do when you execute the workflow. -### Timeout Doesn't Stop Step Execution +So, you can only pass variables as parameters to steps. But, in a workflow, you can't change a variable's value or, if the variable is an array, loop over its items. -Configuring a timeout doesn't stop the execution of a step in progress. The timeout only affects the status of the workflow and its result. +Instead, use `transform` from the Workflows SDK. + +Restrictions for variable manipulation is only applicable in a workflow's definition. You can still manipulate variables in your step's code. *** -## Configure Workflow Timeout +## What is the transform Utility? -The `createWorkflow` function can accept a configuration object instead of the workflow’s name. +`transform` creates a new variable as the result of manipulating other variables. -In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds. +For example, consider you have two strings as the output of two steps: -For example: +```ts +const str1 = step1() +const str2 = step2() +``` -```ts title="src/workflows/hello-world.ts" highlights={[["16"]]} collapsibleLines="1-13" expandButtonLabel="Show More" +To concatenate the strings, you create a new variable `str3` using the `transform` function: + +```ts highlights={highlights} import { - createStep, createWorkflow, WorkflowResponse, + transform, } from "@medusajs/framework/workflows-sdk" +// step imports... -const step1 = createStep( - "step-1", - async () => { - // ... - } -) +const myWorkflow = createWorkflow( + "hello-world", + function (input) { + const str1 = step1(input) + const str2 = step2(input) -const myWorkflow = createWorkflow({ - name: "hello-world", - timeout: 2, // 2 seconds -}, function () { - const str1 = step1() + const str3 = transform( + { str1, str2 }, + (data) => `${data.str1}${data.str2}` + ) - return new WorkflowResponse({ - message: str1, - }) -}) + return new WorkflowResponse(str3) + } +) +``` -export default myWorkflow +`transform` accepts two parameters: -``` +1. The first parameter is an object of variables to manipulate. The object is passed as a parameter to `transform`'s second parameter function. +2. The second parameter is the function performing the variable manipulation. -This workflow's executions fail if they run longer than two seconds. +The value returned by the second parameter function is returned by `transform`. So, the `str3` variable holds the concatenated string. -A workflow’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionTimeoutError`. +You can use the returned value in the rest of the workflow, either to pass it as an input to other steps or to return it in the workflow's response. *** -## Configure Step Timeout +## Example: Looping Over Array -Alternatively, you can configure the timeout for a step rather than the entire workflow. +Use `transform` to loop over arrays to create another variable from the array's items. -As mentioned in the previous section, the timeout doesn't stop the execution of the step. It only affects the step's status and output. +For example: -The step’s configuration object accepts a `timeout` property, whose value is a number indicating the timeout in seconds. +```ts collapsibleLines="1-7" expandButtonLabel="Show Imports" +import { + createWorkflow, + WorkflowResponse, + transform, +} from "@medusajs/framework/workflows-sdk" +// step imports... -For example: +type WorkflowInput = { + items: { + id: string + name: string + }[] +} + +const myWorkflow = createWorkflow( + "hello-world", + function ({ items }: WorkflowInput) { + const ids = transform( + { items }, + (data) => data.items.map((item) => item.id) + ) + + doSomethingStep(ids) -```tsx -const step1 = createStep( - { - name: "step-1", - timeout: 2, // 2 seconds - }, - async () => { // ... } ) ``` -This step's executions fail if they run longer than two seconds. +This workflow receives an `items` array in its input. -A step’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionStepTimeoutError`. +You use `transform` to create an `ids` variable, which is an array of strings holding the `id` of each item in the `items` array. +You then pass the `ids` variable as a parameter to the `doSomethingStep`. -# Write Tests for Modules +*** -In this chapter, you'll learn about `moduleIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests for a module's main service. +## Example: Creating a Date -### Prerequisites +If you create a date with `new Date()` in a workflow's constructor function, Medusa evaluates the date's value when it creates the internal representation of the workflow, not when the workflow is executed. -- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md) +So, use `transform` instead to create a date variable with `new Date()`. -## moduleIntegrationTestRunner Utility +For example: -`moduleIntegrationTestRunner` creates integration tests for a module. The integration tests run on a test Medusa application with only the specified module enabled. +```ts +const myWorkflow = createWorkflow( + "hello-world", + () => { + const today = transform({}, () => new Date()) -For example, assuming you have a `hello` module, create a test file at `src/modules/hello/__tests__/service.spec.ts`: + doSomethingStep(today) + } +) +``` -```ts title="src/modules/hello/__tests__/service.spec.ts" -import { moduleIntegrationTestRunner } from "@medusajs/test-utils" -import { HELLO_MODULE } from ".." -import HelloModuleService from "../service" -import MyCustom from "../models/my-custom" +In this workflow, `today` is only evaluated when the workflow is executed. -moduleIntegrationTestRunner({ - moduleName: HELLO_MODULE, - moduleModels: [MyCustom], - resolve: "./src/modules/hello", - testSuite: ({ service }) => { - // TODO write tests - }, -}) +*** -jest.setTimeout(60 * 1000) +## Caveats + +### Transform Evaluation + +`transform`'s value is only evaluated if you pass its output to a step or in the workflow response. + +For example, if you have the following workflow: + +```ts +const myWorkflow = createWorkflow( + "hello-world", + function (input) { + const str = transform( + { input }, + (data) => `${data.input.str1}${data.input.str2}` + ) + + return new WorkflowResponse("done") + } +) ``` -The `moduleIntegrationTestRunner` function accepts as a parameter an object with the following properties: +Since `str`'s value isn't used as a step's input or passed to `WorkflowResponse`, its value is never evaluated. -- `moduleName`: The name of the module. -- `moduleModels`: An array of models in the module. Refer to [this section](#write-tests-for-modules-without-data-models) if your module doesn't have data models. -- `resolve`: The path to the model. -- `testSuite`: A function that defines the tests to run. +### Data Validation -The `testSuite` function accepts as a parameter an object having the `service` property, which is an instance of the module's main service. +`transform` should only be used to perform variable or data manipulation. -The type argument provided to the `moduleIntegrationTestRunner` function is used as the type of the `service` property. +If you want to perform some validation on the data, use a step or [when-then](https://docs.medusajs.com/learn/fundamentals/workflows/conditions/index.html.md) instead. -The tests in the `testSuite` function are written using [Jest](https://jestjs.io/). +For example: -*** +```ts +// DON'T +const myWorkflow = createWorkflow( + "hello-world", + function (input) { + const str = transform( + { input }, + (data) => { + if (!input.str1) { + throw new Error("Not allowed!") + } + } + ) + } +) -## Run Tests +// DO +const validateHasStr1Step = createStep( + "validate-has-str1", + ({ input }) => { + if (!input.str1) { + throw new Error("Not allowed!") + } + } +) -Run the following command to run your module integration tests: +const myWorkflow = createWorkflow( + "hello-world", + function (input) { + validateHasStr1({ + input, + }) -```bash npm2yarn -npm run test:integration:modules + // workflow continues its execution only if + // the step doesn't throw the error. + } +) ``` -If you don't have a `test:integration:modules` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md). -This runs your Medusa application and runs the tests available in any `__tests__` directory under the `src/modules` directory. +# Workflow Timeout -*** +In this chapter, you’ll learn how to set a timeout for workflows and steps. -## Pass Module Options +## What is a Workflow Timeout? -If your module accepts options, you can set them using the `moduleOptions` property of the `moduleIntegrationTestRunner`'s parameter. +By default, a workflow doesn’t have a timeout. It continues execution until it’s finished or an error occurs. -For example: +You can configure a workflow’s timeout to indicate how long the workflow can execute. If a workflow's execution time passes the configured timeout, it is failed and an error is thrown. -```ts -import { moduleIntegrationTestRunner } from "@medusajs/test-utils" -import HelloModuleService from "../service" +### Timeout Doesn't Stop Step Execution -moduleIntegrationTestRunner({ - moduleOptions: { - apiKey: "123", - }, - // ... -}) -``` +Configuring a timeout doesn't stop the execution of a step in progress. The timeout only affects the status of the workflow and its result. *** -## Write Tests for Modules without Data Models +## Configure Workflow Timeout -If your module doesn't have a data model, pass a dummy model in the `moduleModels` property. +The `createWorkflow` function can accept a configuration object instead of the workflow’s name. + +In the configuration object, you pass a `timeout` property, whose value is a number indicating the timeout in seconds. For example: -```ts -import { moduleIntegrationTestRunner } from "@medusajs/test-utils" -import HelloModuleService from "../service" -import { model } from "@medusajs/framework/utils" +```ts title="src/workflows/hello-world.ts" highlights={[["16"]]} collapsibleLines="1-13" expandButtonLabel="Show More" +import { + createStep, + createWorkflow, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" -const DummyModel = model.define("dummy_model", { - id: model.id().primaryKey(), -}) +const step1 = createStep( + "step-1", + async () => { + // ... + } +) -moduleIntegrationTestRunner({ - moduleModels: [DummyModel], - // ... +const myWorkflow = createWorkflow({ + name: "hello-world", + timeout: 2, // 2 seconds +}, function () { + const str1 = step1() + + return new WorkflowResponse({ + message: str1, + }) }) -jest.setTimeout(60 * 1000) -``` +export default myWorkflow -*** +``` -### Other Options and Inputs +This workflow's executions fail if they run longer than two seconds. -Refer to [this reference in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function. +A workflow’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionTimeoutError`. *** -## Database Used in Tests +## Configure Step Timeout -The `moduleIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end. +Alternatively, you can configure the timeout for a step rather than the entire workflow. -To manage that database, such as changing its name or perform operations on it in your tests, refer to the [references in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/moduleIntegrationTestRunner/index.html.md). +As mentioned in the previous section, the timeout doesn't stop the execution of the step. It only affects the step's status and output. +The step’s configuration object accepts a `timeout` property, whose value is a number indicating the timeout in seconds. -# Write Integration Tests +For example: -In this chapter, you'll learn about `medusaIntegrationTestRunner` from Medusa's Testing Framework and how to use it to write integration tests. +```tsx +const step1 = createStep( + { + name: "step-1", + timeout: 2, // 2 seconds + }, + async () => { + // ... + } +) +``` -### Prerequisites +This step's executions fail if they run longer than two seconds. -- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md) +A step’s timeout error is returned in the `errors` property of the workflow’s execution, as explained in [this chapter](https://docs.medusajs.com/learn/fundamentals/workflows/access-workflow-errors/index.html.md). The error’s name is `TransactionStepTimeoutError`. -## medusaIntegrationTestRunner Utility -The `medusaIntegrationTestRunner` is from Medusa's Testing Framework and it's used to create integration tests in your Medusa project. It runs a full Medusa application, allowing you test API routes, workflows, or other customizations. +# Example: Write Integration Tests for Workflows -For example: +In this chapter, you'll learn how to write integration tests for workflows using [medusaIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests/index.html.md) from Medusa's Testing Framwork. -```ts title="integration-tests/http/test.spec.ts" highlights={highlights} -import { medusaIntegrationTestRunner } from "@medusajs/test-utils" +### Prerequisites -medusaIntegrationTestRunner({ - testSuite: ({ api, getContainer }) => { - // TODO write tests... - }, -}) +- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md) -jest.setTimeout(60 * 1000) -``` +## Write Integration Test for Workflow -The `medusaIntegrationTestRunner` function accepts an object as a parameter. The object has a required property `testSuite`. +Consider you have the following workflow defined at `src/workflows/hello-world.ts`: -`testSuite`'s value is a function that defines the tests to run. The function accepts as a parameter an object that has the following properties: +```ts title="src/workflows/hello-world.ts" +import { + createWorkflow, + createStep, + StepResponse, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" -- `api`: a set of utility methods used to send requests to the Medusa application. It has the following methods: - - `get`: Send a `GET` request to an API route. - - `post`: Send a `POST` request to an API route. - - `delete`: Send a `DELETE` request to an API route. -- `getContainer`: a function that retrieves the Medusa Container. Use the `getContainer().resolve` method to resolve resources from the Medusa Container. +const step1 = createStep("step-1", () => { + return new StepResponse("Hello, World!") +}) -The tests in the `testSuite` function are written using [Jest](https://jestjs.io/). +export const helloWorldWorkflow = createWorkflow( + "hello-world-workflow", + () => { + const message = step1() + + return new WorkflowResponse(message) + } +) +``` + +To write a test for this workflow, create the file `integration-tests/http/workflow.spec.ts` with the following content: + +```ts title="integration-tests/http/workflow.spec.ts" +import { medusaIntegrationTestRunner } from "@medusajs/test-utils" +import { helloWorldWorkflow } from "../../src/workflows/hello-world" + +medusaIntegrationTestRunner({ + testSuite: ({ getContainer }) => { + describe("Test hello-world workflow", () => { + it("returns message", async () => { + const { result } = await helloWorldWorkflow(getContainer()) + .run() + + expect(result).toEqual("Hello, World!") + }) + }) + }, +}) + +jest.setTimeout(60 * 1000) +``` + +You use the `medusaIntegrationTestRunner` to write an integration test for the workflow. The test pases if the workflow returns the string `"Hello, World!"`. ### Jest Timeout Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test: -```ts title="integration-tests/http/test.spec.ts" +```ts title="integration-tests/http/custom-routes.spec.ts" // in your test's file jest.setTimeout(60 * 1000) ``` *** -### Run Tests +## Run Test Run the following command to run your tests: @@ -13723,27 +13825,56 @@ npm run test:integration If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md). -This runs your Medusa application and runs the tests available under the `src/integrations/http` directory. +This runs your Medusa application and runs the tests available under the `integrations/http` directory. *** -## Other Options and Inputs +## Test That a Workflow Throws an Error -Refer to [this reference in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md) for other available parameter options and inputs of the `testSuite` function. +You might want to test that a workflow throws an error in certain cases. To test this: -*** +- Disable the `throwOnError` option when executing the workflow. +- Use the returned `errors` property to check what errors were thrown. -## Database Used in Tests +For example, if you have a step that throws this error: -The `medusaIntegrationTestRunner` function creates a database with a random name before running the tests. Then, it drops that database after all the tests end. +```ts title="src/workflows/hello-world.ts" +import { MedusaError } from "@medusajs/framework/utils" +import { createStep } from "@medusajs/framework/workflows-sdk" -To manage that database, such as changing its name or perform operations on it in your tests, refer to the [references in the Development Resources documentation](https://docs.medusajs.com/resources/test-tools-reference/medusaIntegrationTestRunner/index.html.md). +const step1 = createStep("step-1", () => { + throw new MedusaError(MedusaError.Types.NOT_FOUND, "Item doesn't exist") +}) +``` -*** +You can write the following test to ensure that the workflow throws that error: -## Example Integration Tests +```ts title="integration-tests/http/workflow.spec.ts" +import { medusaIntegrationTestRunner } from "@medusajs/test-utils" +import { helloWorldWorkflow } from "../../src/workflows/hello-world" -The next chapters provide examples of writing integration tests for API routes and workflows. +medusaIntegrationTestRunner({ + testSuite: ({ getContainer }) => { + describe("Test hello-world workflow", () => { + it("returns message", async () => { + const { errors } = await helloWorldWorkflow(getContainer()) + .run({ + throwOnError: false, + }) + + expect(errors.length).toBeGreaterThan(0) + expect(errors[0].error.message).toBe("Item doesn't exist") + }) + }) + }, +}) + +jest.setTimeout(60 * 1000) +``` + +The `errors` property contains an array of errors thrown during the execution of the workflow. Each error item has an `error` object, being the error thrown. + +If you threw a `MedusaError`, then you can check the error message in `errors[0].error.message`. # Example: Integration Tests for a Module @@ -14382,137 +14513,6 @@ const response = await api.post(`/custom`, form, { ``` -# Example: Write Integration Tests for Workflows - -In this chapter, you'll learn how to write integration tests for workflows using [medusaIntegrationTestRunner](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests/index.html.md) from Medusa's Testing Framwork. - -### Prerequisites - -- [Testing Tools Setup](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/index.html.md) - -## Write Integration Test for Workflow - -Consider you have the following workflow defined at `src/workflows/hello-world.ts`: - -```ts title="src/workflows/hello-world.ts" -import { - createWorkflow, - createStep, - StepResponse, - WorkflowResponse, -} from "@medusajs/framework/workflows-sdk" - -const step1 = createStep("step-1", () => { - return new StepResponse("Hello, World!") -}) - -export const helloWorldWorkflow = createWorkflow( - "hello-world-workflow", - () => { - const message = step1() - - return new WorkflowResponse(message) - } -) -``` - -To write a test for this workflow, create the file `integration-tests/http/workflow.spec.ts` with the following content: - -```ts title="integration-tests/http/workflow.spec.ts" -import { medusaIntegrationTestRunner } from "@medusajs/test-utils" -import { helloWorldWorkflow } from "../../src/workflows/hello-world" - -medusaIntegrationTestRunner({ - testSuite: ({ getContainer }) => { - describe("Test hello-world workflow", () => { - it("returns message", async () => { - const { result } = await helloWorldWorkflow(getContainer()) - .run() - - expect(result).toEqual("Hello, World!") - }) - }) - }, -}) - -jest.setTimeout(60 * 1000) -``` - -You use the `medusaIntegrationTestRunner` to write an integration test for the workflow. The test pases if the workflow returns the string `"Hello, World!"`. - -### Jest Timeout - -Since your tests connect to the database and perform actions that require more time than the typical tests, make sure to increase the timeout in your test: - -```ts title="integration-tests/http/custom-routes.spec.ts" -// in your test's file -jest.setTimeout(60 * 1000) -``` - -*** - -## Run Test - -Run the following command to run your tests: - -```bash npm2yarn -npm run test:integration -``` - -If you don't have a `test:integration` script in `package.json`, refer to the [Medusa Testing Tools chapter](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools#add-test-commands/index.html.md). - -This runs your Medusa application and runs the tests available under the `integrations/http` directory. - -*** - -## Test That a Workflow Throws an Error - -You might want to test that a workflow throws an error in certain cases. To test this: - -- Disable the `throwOnError` option when executing the workflow. -- Use the returned `errors` property to check what errors were thrown. - -For example, if you have a step that throws this error: - -```ts title="src/workflows/hello-world.ts" -import { MedusaError } from "@medusajs/framework/utils" -import { createStep } from "@medusajs/framework/workflows-sdk" - -const step1 = createStep("step-1", () => { - throw new MedusaError(MedusaError.Types.NOT_FOUND, "Item doesn't exist") -}) -``` - -You can write the following test to ensure that the workflow throws that error: - -```ts title="integration-tests/http/workflow.spec.ts" -import { medusaIntegrationTestRunner } from "@medusajs/test-utils" -import { helloWorldWorkflow } from "../../src/workflows/hello-world" - -medusaIntegrationTestRunner({ - testSuite: ({ getContainer }) => { - describe("Test hello-world workflow", () => { - it("returns message", async () => { - const { errors } = await helloWorldWorkflow(getContainer()) - .run({ - throwOnError: false, - }) - - expect(errors.length).toBeGreaterThan(0) - expect(errors[0].error.message).toBe("Item doesn't exist") - }) - }) - }, -}) - -jest.setTimeout(60 * 1000) -``` - -The `errors` property contains an array of errors thrown during the execution of the workflow. Each error item has an `error` object, being the error thrown. - -If you threw a `MedusaError`, then you can check the error message in `errors[0].error.message`. - - # Commerce Modules In this section of the documentation, you'll find guides and references related to Medusa's commerce modules. @@ -14802,22 +14802,22 @@ Medusa provides the following authentication providers out-of-the-box. You can u *** -# Customer Module +# Currency Module -In this section of the documentation, you will find resources to learn more about the Customer Module and how to use it in your application. +In this section of the documentation, you will find resources to learn more about the Currency Module and how to use it in your application. -Medusa has customer related features available out-of-the-box through the Customer Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Customer Module. +Medusa has currency related features available out-of-the-box through the Currency Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Currency Module. Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). -## Customer Features +## Currency Features -- [Customer Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/customer-accounts/index.html.md): Store and manage guest and registered customers in your store. -- [Customer Organization](https://docs.medusajs.com/references/customer/models/index.html.md): Organize customers into groups. This has a lot of benefits and supports many use cases, such as provide discounts for specific customer groups using the [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md). +- [Currency Management and Retrieval](https://docs.medusajs.com/references/currency/listAndCountCurrencies/index.html.md): This module adds all common currencies to your application and allows you to retrieve them. +- [Support Currencies in Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/links-to-other-modules/index.html.md): Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details. *** -## How to Use the Customer Module +## How to Use the Currency Module In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. @@ -14825,45 +14825,46 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows For example: -```ts title="src/workflows/create-customer.ts" highlights={highlights} +```ts title="src/workflows/retrieve-price-with-currency.ts" highlights={highlights} import { createWorkflow, WorkflowResponse, createStep, StepResponse, + transform, } from "@medusajs/framework/workflows-sdk" import { Modules } from "@medusajs/framework/utils" -const createCustomerStep = createStep( - "create-customer", - async ({}, { container }) => { - const customerModuleService = container.resolve(Modules.CUSTOMER) - - const customer = await customerModuleService.createCustomers({ - first_name: "Peter", - last_name: "Hayes", - email: "peter.hayes@example.com", - }) +const retrieveCurrencyStep = createStep( + "retrieve-currency", + async ({}, { container }) => { + const currencyModuleService = container.resolve(Modules.CURRENCY) - return new StepResponse({ customer }, customer.id) - }, - async (customerId, { container }) => { - if (!customerId) { - return - } - const customerModuleService = container.resolve(Modules.CUSTOMER) + const currency = await currencyModuleService + .retrieveCurrency("usd") - await customerModuleService.deleteCustomers([customerId]) + return new StepResponse({ currency }) } ) -export const createCustomerWorkflow = createWorkflow( - "create-customer", - () => { - const { customer } = createCustomerStep() +type Input = { + price: number +} + +export const retrievePriceWithCurrency = createWorkflow( + "create-currency", + (input: Input) => { + const { currency } = retrieveCurrencyStep() + + const formattedPrice = transform({ + input, + currency, + }, (data) => { + return `${data.currency.symbol}${data.input.price}` + }) return new WorkflowResponse({ - customer, + formattedPrice, }) } ) @@ -14873,19 +14874,21 @@ You can then execute the workflow in your custom API routes, scheduled jobs, or ### API Route -```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" import type { MedusaRequest, MedusaResponse, } from "@medusajs/framework/http" -import { createCustomerWorkflow } from "../../workflows/create-customer" +import { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency" export async function GET( req: MedusaRequest, res: MedusaResponse ) { - const { result } = await createCustomerWorkflow(req.scope) - .run() + const { result } = await retrievePriceWithCurrency(req.scope) + .run({ + price: 10, + }) res.send(result) } @@ -14893,19 +14896,21 @@ export async function GET( ### Subscriber -```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" import { type SubscriberConfig, type SubscriberArgs, } from "@medusajs/framework" -import { createCustomerWorkflow } from "../workflows/create-customer" +import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency" export default async function handleUserCreated({ event: { data }, container, }: SubscriberArgs<{ id: string }>) { - const { result } = await createCustomerWorkflow(container) - .run() + const { result } = await retrievePriceWithCurrency(container) + .run({ + price: 10, + }) console.log(result) } @@ -14917,15 +14922,17 @@ export const config: SubscriberConfig = { ### Scheduled Job -```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} +```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"]]} import { MedusaContainer } from "@medusajs/framework/types" -import { createCustomerWorkflow } from "../workflows/create-customer" +import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency" export default async function myCustomJob( container: MedusaContainer ) { - const { result } = await createCustomerWorkflow(container) - .run() + const { result } = await retrievePriceWithCurrency(container) + .run({ + price: 10, + }) console.log(result) } @@ -14941,22 +14948,24 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# Currency Module +# Cart Module -In this section of the documentation, you will find resources to learn more about the Currency Module and how to use it in your application. +In this section of the documentation, you will find resources to learn more about the Cart Module and how to use it in your application. -Medusa has currency related features available out-of-the-box through the Currency Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Currency Module. +Medusa has cart related features available out-of-the-box through the Cart Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Cart Module. Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). -## Currency Features +## Cart Features -- [Currency Management and Retrieval](https://docs.medusajs.com/references/currency/listAndCountCurrencies/index.html.md): This module adds all common currencies to your application and allows you to retrieve them. -- [Support Currencies in Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/links-to-other-modules/index.html.md): Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details. +- [Cart Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/concepts/index.html.md): Store and manage carts, including their addresses, line items, shipping methods, and more. +- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/promotions/index.html.md): Apply promotions or discounts to line items and shipping methods by adding adjustment lines that are factored into their subtotals. +- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/tax-lines/index.html.md): Apply tax lines to line items and shipping methods. +- [Cart Scoping](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/links-to-other-modules/index.html.md): When used in the Medusa application, Medusa creates links to other commerce modules, scoping a cart to a sales channel, region, and a customer. *** -## How to Use the Currency Module +## How to Use the Cart Module In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. @@ -14964,46 +14973,54 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows For example: -```ts title="src/workflows/retrieve-price-with-currency.ts" highlights={highlights} +```ts title="src/workflows/create-cart.ts" highlights={highlights} import { createWorkflow, WorkflowResponse, createStep, StepResponse, - transform, } from "@medusajs/framework/workflows-sdk" import { Modules } from "@medusajs/framework/utils" -const retrieveCurrencyStep = createStep( - "retrieve-currency", +const createCartStep = createStep( + "create-cart", async ({}, { container }) => { - const currencyModuleService = container.resolve(Modules.CURRENCY) + const cartModuleService = container.resolve(Modules.CART) - const currency = await currencyModuleService - .retrieveCurrency("usd") + const cart = await cartModuleService.createCarts({ + currency_code: "usd", + shipping_address: { + address_1: "1512 Barataria Blvd", + country_code: "us", + }, + items: [ + { + title: "Shirt", + unit_price: 1000, + quantity: 1, + }, + ], + }) - return new StepResponse({ currency }) + return new StepResponse({ cart }, cart.id) + }, + async (cartId, { container }) => { + if (!cartId) { + return + } + const cartModuleService = container.resolve(Modules.CART) + + await cartModuleService.deleteCarts([cartId]) } ) -type Input = { - price: number -} - -export const retrievePriceWithCurrency = createWorkflow( - "create-currency", - (input: Input) => { - const { currency } = retrieveCurrencyStep() - - const formattedPrice = transform({ - input, - currency, - }, (data) => { - return `${data.currency.symbol}${data.input.price}` - }) +export const createCartWorkflow = createWorkflow( + "create-cart", + () => { + const { cart } = createCartStep() return new WorkflowResponse({ - formattedPrice, + cart, }) } ) @@ -15013,21 +15030,19 @@ You can then execute the workflow in your custom API routes, scheduled jobs, or ### API Route -```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" import type { MedusaRequest, MedusaResponse, } from "@medusajs/framework/http" -import { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency" +import { createCartWorkflow } from "../../workflows/create-cart" export async function GET( req: MedusaRequest, res: MedusaResponse ) { - const { result } = await retrievePriceWithCurrency(req.scope) - .run({ - price: 10, - }) + const { result } = await createCartWorkflow(req.scope) + .run() res.send(result) } @@ -15035,21 +15050,19 @@ export async function GET( ### Subscriber -```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" import { type SubscriberConfig, type SubscriberArgs, } from "@medusajs/framework" -import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency" +import { createCartWorkflow } from "../workflows/create-cart" export default async function handleUserCreated({ event: { data }, container, }: SubscriberArgs<{ id: string }>) { - const { result } = await retrievePriceWithCurrency(container) - .run({ - price: 10, - }) + const { result } = await createCartWorkflow(container) + .run() console.log(result) } @@ -15061,17 +15074,15 @@ export const config: SubscriberConfig = { ### Scheduled Job -```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"]]} +```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} import { MedusaContainer } from "@medusajs/framework/types" -import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency" +import { createCartWorkflow } from "../workflows/create-cart" export default async function myCustomJob( container: MedusaContainer ) { - const { result } = await retrievePriceWithCurrency(container) - .run({ - price: 10, - }) + const { result } = await createCartWorkflow(container) + .run() console.log(result) } @@ -15087,24 +15098,22 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# Cart Module +# Customer Module -In this section of the documentation, you will find resources to learn more about the Cart Module and how to use it in your application. +In this section of the documentation, you will find resources to learn more about the Customer Module and how to use it in your application. -Medusa has cart related features available out-of-the-box through the Cart Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Cart Module. +Medusa has customer related features available out-of-the-box through the Customer Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Customer Module. Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). -## Cart Features +## Customer Features -- [Cart Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/concepts/index.html.md): Store and manage carts, including their addresses, line items, shipping methods, and more. -- [Apply Promotion Adjustments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/promotions/index.html.md): Apply promotions or discounts to line items and shipping methods by adding adjustment lines that are factored into their subtotals. -- [Apply Tax Lines](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/tax-lines/index.html.md): Apply tax lines to line items and shipping methods. -- [Cart Scoping](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/links-to-other-modules/index.html.md): When used in the Medusa application, Medusa creates links to other commerce modules, scoping a cart to a sales channel, region, and a customer. +- [Customer Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/customer-accounts/index.html.md): Store and manage guest and registered customers in your store. +- [Customer Organization](https://docs.medusajs.com/references/customer/models/index.html.md): Organize customers into groups. This has a lot of benefits and supports many use cases, such as provide discounts for specific customer groups using the [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md). *** -## How to Use the Cart Module +## How to Use the Customer Module In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. @@ -15112,7 +15121,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows For example: -```ts title="src/workflows/create-cart.ts" highlights={highlights} +```ts title="src/workflows/create-customer.ts" highlights={highlights} import { createWorkflow, WorkflowResponse, @@ -15121,45 +15130,36 @@ import { } from "@medusajs/framework/workflows-sdk" import { Modules } from "@medusajs/framework/utils" -const createCartStep = createStep( - "create-cart", +const createCustomerStep = createStep( + "create-customer", async ({}, { container }) => { - const cartModuleService = container.resolve(Modules.CART) + const customerModuleService = container.resolve(Modules.CUSTOMER) - const cart = await cartModuleService.createCarts({ - currency_code: "usd", - shipping_address: { - address_1: "1512 Barataria Blvd", - country_code: "us", - }, - items: [ - { - title: "Shirt", - unit_price: 1000, - quantity: 1, - }, - ], + const customer = await customerModuleService.createCustomers({ + first_name: "Peter", + last_name: "Hayes", + email: "peter.hayes@example.com", }) - return new StepResponse({ cart }, cart.id) + return new StepResponse({ customer }, customer.id) }, - async (cartId, { container }) => { - if (!cartId) { + async (customerId, { container }) => { + if (!customerId) { return } - const cartModuleService = container.resolve(Modules.CART) + const customerModuleService = container.resolve(Modules.CUSTOMER) - await cartModuleService.deleteCarts([cartId]) + await customerModuleService.deleteCustomers([customerId]) } ) -export const createCartWorkflow = createWorkflow( - "create-cart", +export const createCustomerWorkflow = createWorkflow( + "create-customer", () => { - const { cart } = createCartStep() + const { customer } = createCustomerStep() return new WorkflowResponse({ - cart, + customer, }) } ) @@ -15174,13 +15174,13 @@ import type { MedusaRequest, MedusaResponse, } from "@medusajs/framework/http" -import { createCartWorkflow } from "../../workflows/create-cart" +import { createCustomerWorkflow } from "../../workflows/create-customer" export async function GET( req: MedusaRequest, res: MedusaResponse ) { - const { result } = await createCartWorkflow(req.scope) + const { result } = await createCustomerWorkflow(req.scope) .run() res.send(result) @@ -15194,13 +15194,13 @@ import { type SubscriberConfig, type SubscriberArgs, } from "@medusajs/framework" -import { createCartWorkflow } from "../workflows/create-cart" +import { createCustomerWorkflow } from "../workflows/create-customer" export default async function handleUserCreated({ event: { data }, container, }: SubscriberArgs<{ id: string }>) { - const { result } = await createCartWorkflow(container) + const { result } = await createCustomerWorkflow(container) .run() console.log(result) @@ -15215,12 +15215,12 @@ export const config: SubscriberConfig = { ```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} import { MedusaContainer } from "@medusajs/framework/types" -import { createCartWorkflow } from "../workflows/create-cart" +import { createCustomerWorkflow } from "../workflows/create-customer" export default async function myCustomJob( container: MedusaContainer ) { - const { result } = await createCartWorkflow(container) + const { result } = await createCustomerWorkflow(container) .run() console.log(result) @@ -15694,168 +15694,15 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# Payment Module +# Pricing Module -In this section of the documentation, you will find resources to learn more about the Payment Module and how to use it in your application. +In this section of the documentation, you will find resources to learn more about the Pricing Module and how to use it in your application. -Medusa has payment related features available out-of-the-box through the Payment Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Payment Module. +Medusa has pricing related features available out-of-the-box through the Pricing Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Pricing Module. Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). -## Payment Features - -- [Authorize, Capture, and Refund Payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md): Authorize, capture, and refund payments for a single resource. -- [Payment Collection Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection/index.html.md): Store and manage all payments of a single resources, such as a cart, in payment collections. -- [Integrate Third-Party Payment Providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md): Use payment providers like [Stripe](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md) to handle and process payments, or integrate custom payment providers. -- [Saved Payment Methods](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/account-holder/index.html.md): Save payment methods for customers in third-party payment providers. -- [Handle Webhook Events](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/webhook-events/index.html.md): Handle webhook events from third-party providers and process the associated payment. - -*** - -## How to Use the Payment Module - -In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. - -You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package. - -For example: - -```ts title="src/workflows/create-payment-collection.ts" highlights={highlights} -import { - createWorkflow, - WorkflowResponse, - createStep, - StepResponse, -} from "@medusajs/framework/workflows-sdk" -import { Modules } from "@medusajs/framework/utils" - -const createPaymentCollectionStep = createStep( - "create-payment-collection", - async ({}, { container }) => { - const paymentModuleService = container.resolve(Modules.PAYMENT) - - const paymentCollection = await paymentModuleService.createPaymentCollections({ - currency_code: "usd", - amount: 5000, - }) - - return new StepResponse({ paymentCollection }, paymentCollection.id) - }, - async (paymentCollectionId, { container }) => { - if (!paymentCollectionId) { - return - } - const paymentModuleService = container.resolve(Modules.PAYMENT) - - await paymentModuleService.deletePaymentCollections([paymentCollectionId]) - } -) - -export const createPaymentCollectionWorkflow = createWorkflow( - "create-payment-collection", - () => { - const { paymentCollection } = createPaymentCollectionStep() - - return new WorkflowResponse({ - paymentCollection, - }) - } -) -``` - -You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers: - -### API Route - -```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" -import type { - MedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import { createPaymentCollectionWorkflow } from "../../workflows/create-payment-collection" - -export async function GET( - req: MedusaRequest, - res: MedusaResponse -) { - const { result } = await createPaymentCollectionWorkflow(req.scope) - .run() - - res.send(result) -} -``` - -### Subscriber - -```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" -import { - type SubscriberConfig, - type SubscriberArgs, -} from "@medusajs/framework" -import { createPaymentCollectionWorkflow } from "../workflows/create-payment-collection" - -export default async function handleUserCreated({ - event: { data }, - container, -}: SubscriberArgs<{ id: string }>) { - const { result } = await createPaymentCollectionWorkflow(container) - .run() - - console.log(result) -} - -export const config: SubscriberConfig = { - event: "user.created", -} -``` - -### Scheduled Job - -```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} -import { MedusaContainer } from "@medusajs/framework/types" -import { createPaymentCollectionWorkflow } from "../workflows/create-payment-collection" - -export default async function myCustomJob( - container: MedusaContainer -) { - const { result } = await createPaymentCollectionWorkflow(container) - .run() - - console.log(result) -} - -export const config = { - name: "run-once-a-day", - schedule: `0 0 * * *`, -} -``` - -Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md). - -*** - -## Configure Payment Module - -The Payment Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options/index.html.md) for details on the module's options. - -*** - -## Providers - -Medusa provides the following payment providers out-of-the-box. You can use them to process payments for orders, returns, and other resources. - -*** - - -# Pricing Module - -In this section of the documentation, you will find resources to learn more about the Pricing Module and how to use it in your application. - -Medusa has pricing related features available out-of-the-box through the Pricing Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Pricing Module. - -Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). - -## Pricing Features +## Pricing Features - [Price Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/concepts/index.html.md): Store and manage prices of a resource, such as a product or a variant. - [Advanced Rule Engine](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-rules/index.html.md): Create prices with custom rules to condition prices based on different contexts. @@ -15999,24 +15846,25 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# Promotion Module +# Payment Module -In this section of the documentation, you will find resources to learn more about the Promotion Module and how to use it in your application. +In this section of the documentation, you will find resources to learn more about the Payment Module and how to use it in your application. -Medusa has promotion related features available out-of-the-box through the Promotion Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Promotion Module. +Medusa has payment related features available out-of-the-box through the Payment Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Payment Module. Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). -## Promotion Features +## Payment Features -- [Discount Functionalities](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts/index.html.md): A promotion discounts an amount or percentage of a cart's items, shipping methods, or the entire order. -- [Flexible Promotion Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts#flexible-rules/index.html.md): A promotion has rules that restricts when the promotion is applied. -- [Campaign Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/campaign/index.html.md): A campaign combines promotions under the same conditions, such as start and end dates, and budget configurations. -- [Apply Promotion on Carts and Orders](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md): Apply promotions on carts and orders to discount items, shipping methods, or the entire order. +- [Authorize, Capture, and Refund Payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md): Authorize, capture, and refund payments for a single resource. +- [Payment Collection Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection/index.html.md): Store and manage all payments of a single resources, such as a cart, in payment collections. +- [Integrate Third-Party Payment Providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md): Use payment providers like [Stripe](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md) to handle and process payments, or integrate custom payment providers. +- [Saved Payment Methods](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/account-holder/index.html.md): Save payment methods for customers in third-party payment providers. +- [Handle Webhook Events](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/webhook-events/index.html.md): Handle webhook events from third-party providers and process the associated payment. *** -## How to Use the Promotion Module +## How to Use the Payment Module In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. @@ -16024,7 +15872,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows For example: -```ts title="src/workflows/create-promotion.ts" highlights={highlights} +```ts title="src/workflows/create-payment-collection.ts" highlights={highlights} import { createWorkflow, WorkflowResponse, @@ -16033,41 +15881,35 @@ import { } from "@medusajs/framework/workflows-sdk" import { Modules } from "@medusajs/framework/utils" -const createPromotionStep = createStep( - "create-promotion", +const createPaymentCollectionStep = createStep( + "create-payment-collection", async ({}, { container }) => { - const promotionModuleService = container.resolve(Modules.PROMOTION) + const paymentModuleService = container.resolve(Modules.PAYMENT) - const promotion = await promotionModuleService.createPromotions({ - code: "10%OFF", - type: "standard", - application_method: { - type: "percentage", - target_type: "order", - value: 10, - currency_code: "usd", - }, + const paymentCollection = await paymentModuleService.createPaymentCollections({ + currency_code: "usd", + amount: 5000, }) - return new StepResponse({ promotion }, promotion.id) + return new StepResponse({ paymentCollection }, paymentCollection.id) }, - async (promotionId, { container }) => { - if (!promotionId) { + async (paymentCollectionId, { container }) => { + if (!paymentCollectionId) { return } - const promotionModuleService = container.resolve(Modules.PROMOTION) + const paymentModuleService = container.resolve(Modules.PAYMENT) - await promotionModuleService.deletePromotions(promotionId) + await paymentModuleService.deletePaymentCollections([paymentCollectionId]) } ) -export const createPromotionWorkflow = createWorkflow( - "create-promotion", +export const createPaymentCollectionWorkflow = createWorkflow( + "create-payment-collection", () => { - const { promotion } = createPromotionStep() + const { paymentCollection } = createPaymentCollectionStep() return new WorkflowResponse({ - promotion, + paymentCollection, }) } ) @@ -16082,13 +15924,13 @@ import type { MedusaRequest, MedusaResponse, } from "@medusajs/framework/http" -import { createPromotionWorkflow } from "../../workflows/create-cart" +import { createPaymentCollectionWorkflow } from "../../workflows/create-payment-collection" export async function GET( req: MedusaRequest, res: MedusaResponse ) { - const { result } = await createPromotionWorkflow(req.scope) + const { result } = await createPaymentCollectionWorkflow(req.scope) .run() res.send(result) @@ -16102,13 +15944,13 @@ import { type SubscriberConfig, type SubscriberArgs, } from "@medusajs/framework" -import { createPromotionWorkflow } from "../workflows/create-cart" +import { createPaymentCollectionWorkflow } from "../workflows/create-payment-collection" export default async function handleUserCreated({ event: { data }, container, }: SubscriberArgs<{ id: string }>) { - const { result } = await createPromotionWorkflow(container) + const { result } = await createPaymentCollectionWorkflow(container) .run() console.log(result) @@ -16123,12 +15965,12 @@ export const config: SubscriberConfig = { ```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} import { MedusaContainer } from "@medusajs/framework/types" -import { createPromotionWorkflow } from "../workflows/create-cart" +import { createPaymentCollectionWorkflow } from "../workflows/create-payment-collection" export default async function myCustomJob( container: MedusaContainer ) { - const { result } = await createPromotionWorkflow(container) + const { result } = await createPaymentCollectionWorkflow(container) .run() console.log(result) @@ -16144,6 +15986,18 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** +## Configure Payment Module + +The Payment Module accepts options for further configurations. Refer to [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options/index.html.md) for details on the module's options. + +*** + +## Providers + +Medusa provides the following payment providers out-of-the-box. You can use them to process payments for orders, returns, and other resources. + +*** + # Product Module @@ -16297,25 +16151,24 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# Region Module +# Promotion Module -In this section of the documentation, you will find resources to learn more about the Region Module and how to use it in your application. +In this section of the documentation, you will find resources to learn more about the Promotion Module and how to use it in your application. -Medusa has region related features available out-of-the-box through the Region Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Region Module. +Medusa has promotion related features available out-of-the-box through the Promotion Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Promotion Module. Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). -*** - -## Region Features +## Promotion Features -- [Region Management](https://docs.medusajs.com/references/region/models/Region/index.html.md): Manage regions in your store. You can create regions with different currencies and settings. -- [Multi-Currency Support](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has a currency. You can support multiple currencies in your store by creating multiple regions. -- [Different Settings Per Region](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has its own settings, such as what countries belong to a region or its tax settings. +- [Discount Functionalities](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts/index.html.md): A promotion discounts an amount or percentage of a cart's items, shipping methods, or the entire order. +- [Flexible Promotion Rules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts#flexible-rules/index.html.md): A promotion has rules that restricts when the promotion is applied. +- [Campaign Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/campaign/index.html.md): A campaign combines promotions under the same conditions, such as start and end dates, and budget configurations. +- [Apply Promotion on Carts and Orders](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md): Apply promotions on carts and orders to discount items, shipping methods, or the entire order. *** -## How to Use Region Module's Service +## How to Use the Promotion Module In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. @@ -16323,7 +16176,7 @@ You can build custom workflows and steps. You can also re-use Medusa's workflows For example: -```ts title="src/workflows/create-region.ts" highlights={highlights} +```ts title="src/workflows/create-promotion.ts" highlights={highlights} import { createWorkflow, WorkflowResponse, @@ -16332,9 +16185,156 @@ import { } from "@medusajs/framework/workflows-sdk" import { Modules } from "@medusajs/framework/utils" -const createRegionStep = createStep( - "create-region", - async ({}, { container }) => { +const createPromotionStep = createStep( + "create-promotion", + async ({}, { container }) => { + const promotionModuleService = container.resolve(Modules.PROMOTION) + + const promotion = await promotionModuleService.createPromotions({ + code: "10%OFF", + type: "standard", + application_method: { + type: "percentage", + target_type: "order", + value: 10, + currency_code: "usd", + }, + }) + + return new StepResponse({ promotion }, promotion.id) + }, + async (promotionId, { container }) => { + if (!promotionId) { + return + } + const promotionModuleService = container.resolve(Modules.PROMOTION) + + await promotionModuleService.deletePromotions(promotionId) + } +) + +export const createPromotionWorkflow = createWorkflow( + "create-promotion", + () => { + const { promotion } = createPromotionStep() + + return new WorkflowResponse({ + promotion, + }) + } +) +``` + +You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers: + +### API Route + +```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import type { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import { createPromotionWorkflow } from "../../workflows/create-cart" + +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const { result } = await createPromotionWorkflow(req.scope) + .run() + + res.send(result) +} +``` + +### Subscriber + +```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import { + type SubscriberConfig, + type SubscriberArgs, +} from "@medusajs/framework" +import { createPromotionWorkflow } from "../workflows/create-cart" + +export default async function handleUserCreated({ + event: { data }, + container, +}: SubscriberArgs<{ id: string }>) { + const { result } = await createPromotionWorkflow(container) + .run() + + console.log(result) +} + +export const config: SubscriberConfig = { + event: "user.created", +} +``` + +### Scheduled Job + +```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]} +import { MedusaContainer } from "@medusajs/framework/types" +import { createPromotionWorkflow } from "../workflows/create-cart" + +export default async function myCustomJob( + container: MedusaContainer +) { + const { result } = await createPromotionWorkflow(container) + .run() + + console.log(result) +} + +export const config = { + name: "run-once-a-day", + schedule: `0 0 * * *`, +} +``` + +Learn more about workflows in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md). + +*** + + +# Region Module + +In this section of the documentation, you will find resources to learn more about the Region Module and how to use it in your application. + +Medusa has region related features available out-of-the-box through the Region Module. A [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Region Module. + +Learn more about why modules are isolated in [this documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/isolation/index.html.md). + +*** + +## Region Features + +- [Region Management](https://docs.medusajs.com/references/region/models/Region/index.html.md): Manage regions in your store. You can create regions with different currencies and settings. +- [Multi-Currency Support](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has a currency. You can support multiple currencies in your store by creating multiple regions. +- [Different Settings Per Region](https://docs.medusajs.com/references/region/models/Region/index.html.md): Each region has its own settings, such as what countries belong to a region or its tax settings. + +*** + +## How to Use Region Module's Service + +In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism. + +You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package. + +For example: + +```ts title="src/workflows/create-region.ts" highlights={highlights} +import { + createWorkflow, + WorkflowResponse, + createStep, + StepResponse, +} from "@medusajs/framework/workflows-sdk" +import { Modules } from "@medusajs/framework/utils" + +const createRegionStep = createStep( + "create-region", + async ({}, { container }) => { const regionModuleService = container.resolve(Modules.REGION) const region = await regionModuleService.createRegions({ @@ -16870,34 +16870,6 @@ Learn more about workflows in [this documentation](https://docs.medusajs.com/doc *** -# API Key Concepts - -In this document, you’ll learn about the different types of API keys, their expiration and verification. - -## API Key Types - -There are two types of API keys: - -- `publishable`: A public key used in client applications, such as a storefront. -- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token. - -The API key’s type is stored in the `type` property of the [ApiKey data model](https://docs.medusajs.com/references/api-key/models/ApiKey/index.html.md). - -*** - -## API Key Expiration - -An API key expires when it’s revoked using the [revoke method of the module’s main service](https://docs.medusajs.com/references/api-key/revoke/index.html.md). - -The associated token is no longer usable or verifiable. - -*** - -## Token Verification - -To verify a token received as an input or in a request, use the [authenticate method of the module’s main service](https://docs.medusajs.com/references/api-key/authenticate/index.html.md) which validates the token against all non-expired tokens. - - # User Module In this section of the documentation, you will find resources to learn more about the User Module and how to use it in your application. @@ -17185,6 +17157,34 @@ The Tax Module accepts options for further configurations. Refer to [this docume *** +# API Key Concepts + +In this document, you’ll learn about the different types of API keys, their expiration and verification. + +## API Key Types + +There are two types of API keys: + +- `publishable`: A public key used in client applications, such as a storefront. +- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token. + +The API key’s type is stored in the `type` property of the [ApiKey data model](https://docs.medusajs.com/references/api-key/models/ApiKey/index.html.md). + +*** + +## API Key Expiration + +An API key expires when it’s revoked using the [revoke method of the module’s main service](https://docs.medusajs.com/references/api-key/revoke/index.html.md). + +The associated token is no longer usable or verifiable. + +*** + +## Token Verification + +To verify a token received as an input or in a request, use the [authenticate method of the module’s main service](https://docs.medusajs.com/references/api-key/authenticate/index.html.md) which validates the token against all non-expired tokens. + + # Links between API Key Module and Other Modules This document showcases the module links defined between the API Key Module and other commerce modules. @@ -17281,54 +17281,6 @@ createRemoteLinkStep({ ``` -# Auth Providers - -In this document, you’ll learn how the Auth Module handles authentication using providers. - -## What's an Auth Module Provider? - -An auth module provider handles authenticating customers and users, either using custom logic or by integrating a third-party service. - -For example, the EmailPass Auth Module Provider authenticates a user using their email and password, whereas the Google Auth Module Provider authenticates users using their Google account. - -### Auth Providers List - -- [Emailpass](https://docs.medusajs.com/commerce-modules/auth/auth-providers/emailpass/index.html.md) -- [Google](https://docs.medusajs.com/commerce-modules/auth/auth-providers/google/index.html.md) -- [GitHub](https://docs.medusajs.com/commerce-modules/auth/auth-providers/github/index.html.md) - -*** - -## Configure Allowed Auth Providers of Actor Types - -By default, users of all actor types can authenticate with all installed auth module providers. - -To restrict the auth providers used for actor types, use the [authMethodsPerActor option](https://docs.medusajs.com/references/medusa-config#http-authMethodsPerActor-1-3/index.html.md) in Medusa's configurations: - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - http: { - authMethodsPerActor: { - user: ["google"], - customer: ["emailpass"], - }, - // ... - }, - // ... - }, -}) -``` - -When you specify the `authMethodsPerActor` configuration, it overrides the default. So, if you don't specify any providers for an actor type, users of that actor type can't authenticate with any provider. - -*** - -## How to Create an Auth Module Provider - -Refer to [this guide](https://docs.medusajs.com/references/auth/provider/index.html.md) to learn how to create an auth module provider. - - # Auth Identity and Actor Types In this document, you’ll learn about concepts related to identity and actors in the Auth Module. @@ -17398,1282 +17350,1203 @@ For example, if you have a custom module with a `Manager` data model, you can au Learn how to create a custom actor type in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/create-actor-type/index.html.md). -# How to Create an Actor Type +# Authentication Flows with the Auth Main Service -In this document, learn how to create an actor type and authenticate its associated data model. +In this document, you'll learn how to use the Auth Module's main service's methods to implement authentication flows and reset a user's password. -## 0. Create Module with Data Model +## Authentication Methods -Before creating an actor type, you must have a module with a data model representing the actor type. +### Register -Learn how to create a module in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md). +The [register method of the Auth Module's main service](https://docs.medusajs.com/references/auth/register/index.html.md) creates an auth identity that can be authenticated later. -The rest of this guide uses this `Manager` data model as an example: +For example: -```ts title="src/modules/manager/models/manager.ts" -import { model } from "@medusajs/framework/utils" +```ts +const data = await authModuleService.register( + "emailpass", + // passed to auth provider + { + // ... + } +) +``` -const Manager = model.define("manager", { - id: model.id().primaryKey(), - firstName: model.text(), - lastName: model.text(), - email: model.text(), -}) +This method calls the `register` method of the provider specified in the first parameter and returns its data. -export default Manager +### Authenticate + +To authenticate a user, you use the [authenticate method of the Auth Module's main service](https://docs.medusajs.com/references/auth/authenticate/index.html.md). For example: + +```ts +const data = await authModuleService.authenticate( + "emailpass", + // passed to auth provider + { + // ... + } +) ``` -*** +This method calls the `authenticate` method of the provider specified in the first parameter and returns its data. -## 1. Create Workflow +*** -Start by creating a workflow that does two things: +## Auth Flow 1: Basic Authentication -- Creates a record of the `Manager` data model. -- Sets the `app_metadata` property of the associated `AuthIdentity` record based on the new actor type. +The basic authentication flow requires first using the `register` method, then the `authenticate` method: -For example, create the file `src/workflows/create-manager.ts`. with the following content: +```ts +const { success, authIdentity, error } = await authModuleService.register( + "emailpass", + // passed to auth provider + { + // ... + } +) -```ts title="src/workflows/create-manager.ts" highlights={workflowHighlights} -import { - createWorkflow, - createStep, - StepResponse, - WorkflowResponse, -} from "@medusajs/framework/workflows-sdk" -import { - setAuthAppMetadataStep, -} from "@medusajs/medusa/core-flows" -import ManagerModuleService from "../modules/manager/service" +if (error) { + // registration failed + // TODO return an error + return +} -type CreateManagerWorkflowInput = { - manager: { - first_name: string - last_name: string - email: string +// later (can be another route for log-in) +const { success, authIdentity, location } = await authModuleService.authenticate( + "emailpass", + // passed to auth provider + { + // ... } - authIdentityId: string +) + +if (success && !location) { + // user is authenticated } +``` -const createManagerStep = createStep( - "create-manager-step", - async ({ - manager: managerData, - }: Pick, - { container }) => { - const managerModuleService: ManagerModuleService = - container.resolve("managerModuleService") +If `success` is true and `location` isn't set, the user is authenticated successfully, and their authentication details are available within the `authIdentity` object. - const manager = await managerModuleService.createManager( - managerData - ) +The next section explains the flow if `location` is set. - return new StepResponse(manager) - } -) +Check out the [AuthIdentity](https://docs.medusajs.com/references/auth/models/AuthIdentity/index.html.md) reference for the received properties in `authIdentity`. -const createManagerWorkflow = createWorkflow( - "create-manager", - function (input: CreateManagerWorkflowInput) { - const manager = createManagerStep({ - manager: input.manager, - }) +![Diagram showcasing the basic authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711373749/Medusa%20Resources/basic-auth_lgpqsj.jpg) - setAuthAppMetadataStep({ - authIdentityId: input.authIdentityId, - actorType: "manager", - value: manager.id, - }) +### Auth Identity with Same Identifier - return new WorkflowResponse(manager) +If an auth identity, such as a `customer`, tries to register with an email of another auth identity, the `register` method returns an error. This can happen either if another customer is using the same email, or an admin user has the same email. + +There are two ways to handle this: + +- Consider the customer authenticated if the `authenticate` method validates that the email and password are correct. This allows admin users, for example, to authenticate as customers. +- Return an error message to the customer, informing them that the email is already in use. + +*** + +## Auth Flow 2: Third-Party Service Authentication + +The third-party service authentication method requires using the `authenticate` method first: + +```ts +const { success, authIdentity, location } = await authModuleService.authenticate( + "google", + // passed to auth provider + { + // ... } ) -export default createManagerWorkflow +if (location) { + // return the location for the front-end to redirect to +} + +if (!success) { + // authentication failed +} + +// authentication successful ``` -This workflow accepts the manager’s data and the associated auth identity’s ID as inputs. The next sections explain how the auth identity ID is retrieved. +If the `authenticate` method returns a `location` property, the authentication process requires the user to perform an action with a third-party service. So, you return the `location` to the front-end or client to redirect to that URL. -The workflow has two steps: +For example, when using the `google` provider, the `location` is the URL that the user is navigated to login. -1. Create the manager using the `createManagerStep`. -2. Set the `app_metadata` property of the associated auth identity using the `setAuthAppMetadataStep` from Medusa's core workflows. You specify the actor type `manager` in the `actorType` property of the step’s input. +![Diagram showcasing the first part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711374847/Medusa%20Resources/third-party-auth-1_enyedy.jpg) -*** +### Overriding Callback URL -## 2. Define the Create API Route +The Google and GitHub providers allow you to override their `callbackUrl` option during authentication. This is useful when you redirect the user after authentication to a URL based on its actor type. For example, you redirect admin users and customers to different pages. -Next, you’ll use the workflow defined in the previous section in an API route that creates a manager. +```ts +const { success, authIdentity, location } = await authModuleService.authenticate( + "google", + // passed to auth provider + { + // ... + callback_url: "example.com", + } +) +``` -So, create the file `src/api/manager/route.ts` with the following content: +### validateCallback -```ts title="src/api/manager/route.ts" highlights={createRouteHighlights} -import type { - AuthenticatedMedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import { MedusaError } from "@medusajs/framework/utils" -import createManagerWorkflow from "../../workflows/create-manager" +Providers handling this authentication flow must implement the `validateCallback` method. It implements the logic to validate the authentication with the third-party service. -type RequestBody = { - first_name: string - last_name: string - email: string -} +So, once the user performs the required action with the third-party service (for example, log-in with Google), the frontend must redirect to an API route that uses the [validateCallback method of the Auth Module's main service](https://docs.medusajs.com/references/auth/validateCallback/index.html.md). -export async function POST( - req: AuthenticatedMedusaRequest, - res: MedusaResponse -) { - // If `actor_id` is present, the request carries - // authentication for an existing manager - if (req.auth_context.actor_id) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - "Request already authenticated as a manager." - ) +The method calls the specified provider’s `validateCallback` method passing it the authentication details it received in the second parameter: + +```ts +const { success, authIdentity } = await authModuleService.validateCallback( + "google", + // passed to auth provider + { + // request data, such as + url, + headers, + query, + body, + protocol, } +) - const { result } = await createManagerWorkflow(req.scope) - .run({ - input: { - manager: req.body, - authIdentityId: req.auth_context.auth_identity_id, - }, - }) - - res.status(200).json({ manager: result }) +if (success) { + // authentication succeeded } ``` -Since the manager must be associated with an `AuthIdentity` record, the request is expected to be authenticated, even if the manager isn’t created yet. This can be achieved by: +For providers like Google, the `query` object contains the query parameters from the original callback URL, such as the `code` and `state` parameters. -1. Obtaining a token usng the [/auth route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md). -2. Passing the token in the bearer header of the request to this route. +If the returned `success` property is `true`, the authentication with the third-party provider was successful. -In the API route, you create the manager using the workflow from the previous section and return it in the response. +![Diagram showcasing the second part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711375123/Medusa%20Resources/third-party-auth-2_kmjxju.jpg) *** -## 3. Apply the `authenticate` Middleware +## Reset Password -The last step is to apply the `authenticate` middleware on the API routes that require a manager’s authentication. +To update a user's password or other authentication details, use the `updateProvider` method of the Auth Module's main service. It calls the `update` method of the specified authentication provider. -To do that, create the file `src/api/middlewares.ts` with the following content: +For example: -```ts title="src/api/middlewares.ts" highlights={middlewareHighlights} -import { - defineMiddlewares, - authenticate, -} from "@medusajs/framework/http" +```ts +const { success } = await authModuleService.updateProvider( + "emailpass", + // passed to the auth provider + { + entity_id: "user@example.com", + password: "supersecret", + } +) -export default defineMiddlewares({ - routes: [ - { - matcher: "/manager", - method: "POST", - middlewares: [ - authenticate("manager", ["session", "bearer"], { - allowUnregistered: true, - }), - ], - }, - { - matcher: "/manager/me*", - middlewares: [ - authenticate("manager", ["session", "bearer"]), - ], - }, - ], -}) +if (success) { + // password reset successfully +} ``` -This applies middlewares on two route patterns: - -1. The `authenticate` middleware is applied on the `/manager` API route for `POST` requests while allowing unregistered managers. This requires that a bearer token be passed in the request to access the manager’s auth identity but doesn’t require the manager to be registered. -2. The `authenticate` middleware is applied on all routes starting with `/manager/me`, restricting these routes to authenticated managers only. +The method accepts as a first parameter the ID of the provider, and as a second parameter the data necessary to reset the password. -### Retrieve Manager API Route +In the example above, you use the `emailpass` provider, so you have to pass an object having an `email` and `password` properties. -For example, create the file `src/api/manager/me/route.ts` with the following content: +If the returned `success` property is `true`, the password has reset successfully. -```ts title="src/api/manager/me/route.ts" -import { - AuthenticatedMedusaRequest, - MedusaResponse, -} from "@medusajs/framework/http" -import ManagerModuleService from "../../../modules/manager/service" -export async function GET( - req: AuthenticatedMedusaRequest, - res: MedusaResponse -): Promise { - const managerModuleService: ManagerModuleService = - req.scope.resolve("managerModuleService") +# How to Use Authentication Routes - const manager = await managerModuleService.retrieveManager( - req.auth_context.actor_id - ) +In this document, you'll learn about the authentication routes and how to use them to create and log-in users, and reset their password. - res.json({ manager }) -} -``` +These routes are added by Medusa's HTTP layer, not the Auth Module. -This route is only accessible by authenticated managers. You access the manager’s ID using `req.auth_context.actor_id`. +## Types of Authentication Flows -*** +### 1. Basic Authentication Flow -## Test Custom Actor Type Authentication Flow +This authentication flow doesn't require validation with third-party services. -To authenticate managers: +[How to register customer in storefront using basic authentication flow](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md). -1. Send a `POST` request to `/auth/manager/emailpass/register` to create an auth identity for the manager: +The steps are: -```bash -curl -X POST 'http://localhost:9000/auth/manager/emailpass/register' \ --H 'Content-Type: application/json' \ ---data-raw '{ - "email": "manager@gmail.com", - "password": "supersecret" -}' -``` +![Diagram showcasing the basic authentication flow between the frontend and the Medusa application](https://res.cloudinary.com/dza7lstvk/image/upload/v1725539370/Medusa%20Resources/basic-auth-routes_pgpjch.jpg) -Copy the returned token to use it in the next request. +1. Register the user with the [Register Route](#register-route). +2. Use the authentication token to create the user with their respective API route. + - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers). + - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept) +3. Authenticate the user with the [Auth Route](#login-route). -2. Send a `POST` request to `/manager` to create a manager: +After registration, you only use the [Auth Route](#login-route) for subsequent authentication. -```bash -curl -X POST 'http://localhost:9000/manager' \ --H 'Content-Type: application/json' \ --H 'Authorization: Bearer {token}' \ ---data-raw '{ - "first_name": "John", - "last_name": "Doe", - "email": "manager@gmail.com" -}' -``` +To handle errors related to existing identities, refer to [this section](#handling-existing-identities). -Replace `{token}` with the token returned in the previous step. +### 2. Third-Party Service Authenticate Flow -3. Send a `POST` request to `/auth/manager/emailpass` again to retrieve an authenticated token for the manager: +This authentication flow authenticates the user with a third-party service, such as Google. -```bash -curl -X POST 'http://localhost:9000/auth/manager/emailpass' \ --H 'Content-Type: application/json' \ ---data-raw '{ - "email": "manager@gmail.com", - "password": "supersecret" -}' -``` +[How to authenticate customer with a third-party provider in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md). -4. You can now send authenticated requests as a manager. For example, send a `GET` request to `/manager/me` to retrieve the authenticated manager’s details: +It requires the following steps: -```bash -curl 'http://localhost:9000/manager/me' \ --H 'Authorization: Bearer {token}' -``` +![Diagram showcasing the authentication flow between the frontend, Medusa application, and third-party service](https://res.cloudinary.com/dza7lstvk/image/upload/v1725528159/Medusa%20Resources/Third_Party_Auth_tvf4ng.jpg) -Whenever you want to log in as a manager, use the `/auth/manager/emailpass` API route, as explained in step 3. +1. Authenticate the user with the [Auth Route](#login-route). +2. The auth route returns a URL to authenticate with third-party service, such as login with Google. The frontend (such as a storefront), when it receives a `location` property in the response, must redirect to the returned location. +3. Once the authentication with the third-party service finishes, it redirects back to the frontend with a `code` query parameter. So, make sure your third-party service is configured to redirect to your frontend page after successful authentication. +4. The frontend sends a request to the [Validate Callback Route](#validate-callback-route) passing it the query parameters received from the third-party service, such as the `code` and `state` query parameters. +5. If the callback validation is successful, the frontend receives the authentication token. +6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt). + - If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests. + - If not, follow the rest of the steps. +7. The frontend uses the authentication token to create the user with their respective API route. + - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers). + - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept) +8. The frontend sends a request to the [Refresh Token Route](#refresh-token-route) to retrieve a new token with the user information populated. *** -## Delete User of Actor Type +## Register Route -When you delete a user of the actor type, you must update its auth identity to remove the association to the user. +The Medusa application defines an API route at `/auth/{actor_type}/{provider}/register` that creates an auth identity for an actor type, such as a `customer`. It returns a JWT token that you pass to an API route that creates the user. -For example, create the following workflow that deletes a manager and updates its auth identity, create the file `src/workflows/delete-manager.ts` with the following content: +```bash +curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/register +-H 'Content-Type: application/json' \ +--data-raw '{ + "email": "Whitney_Schultz@gmail.com" + // ... +}' +``` -```ts title="src/workflows/delete-manager.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports" -import { - createStep, - StepResponse, -} from "@medusajs/framework/workflows-sdk" -import ManagerModuleService from "../modules/manager/service" +This API route is useful for providers like `emailpass` that uses custom logic to authenticate a user. For authentication providers that authenticate with third-party services, such as Google, use the [Auth Route](#login-route) instead. -export type DeleteManagerWorkflow = { - id: string -} +For example, if you're registering a customer, you: -const deleteManagerStep = createStep( - "delete-manager-step", - async ( - { id }: DeleteManagerWorkflow, - { container }) => { - const managerModuleService: ManagerModuleService = - container.resolve("managerModuleService") +1. Send a request to `/auth/customer/emailpass/register` to retrieve the registration JWT token. +2. Send a request to the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers) to create the customer, passing the [JWT token in the header](https://docs.medusajs.com/api/store#authentication). - const manager = await managerModuleService.retrieve(id) +### Path Parameters - await managerModuleService.deleteManagers(id) +Its path parameters are: - return new StepResponse(undefined, { manager }) - }, - async ({ manager }, { container }) => { - const managerModuleService: ManagerModuleService = - container.resolve("managerModuleService") +- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. +- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`. - await managerModuleService.createManagers(manager) - } - ) -``` +### Request Body Parameters -You add a step that deletes the manager using the `deleteManagers` method of the module's main service. In the compensation function, you create the manager again. +This route accepts in the request body the data that the specified authentication provider requires to handle authentication. -Next, in the same file, add the workflow that deletes a manager: +For example, the EmailPass provider requires an `email` and `password` fields in the request body. -```ts title="src/workflows/delete-manager.ts" collapsibleLines="1-15" expandButtonLabel="Show Imports" highlights={deleteHighlights} -// other imports -import { MedusaError } from "@medusajs/framework/utils" -import { - WorkflowData, - WorkflowResponse, - createWorkflow, - transform, -} from "@medusajs/framework/workflows-sdk" -import { - setAuthAppMetadataStep, - useQueryGraphStep, -} from "@medusajs/medusa/core-flows" +### Response Fields -// ... +If the authentication is successful, you'll receive a `token` field in the response body object: -export const deleteManagerWorkflow = createWorkflow( - "delete-manager", - ( - input: WorkflowData - ): WorkflowResponse => { - deleteManagerStep(input) +```json +{ + "token": "..." +} +``` - const { data: authIdentities } = useQueryGraphStep({ - entity: "auth_identity", - fields: ["id"], - filters: { - app_metadata: { - // the ID is of the format `{actor_type}_id`. - manager_id: input.id, - }, - }, - }) +Use that token in the header of subsequent requests to send authenticated requests. - const authIdentity = transform( - { authIdentities }, - ({ authIdentities }) => { - const authIdentity = authIdentities[0] +### Handling Existing Identities - if (!authIdentity) { - throw new MedusaError( - MedusaError.Types.NOT_FOUND, - "Auth identity not found" - ) - } +An auth identity with the same email may already exist in Medusa. This can happen if: - return authIdentity - } - ) +- Another actor type is using that email. For example, an admin user is trying to register as a customer. +- The same email belongs to a record of the same actor type. For example, another customer has the same email. - setAuthAppMetadataStep({ - authIdentityId: authIdentity.id, - actorType: "manager", - value: null, - }) +In these scenarios, the Register Route will return an error instead of a token: - return new WorkflowResponse(input.id) - } -) +```json +{ + "type": "unauthorized", + "message": "Identity with email already exists" +} ``` -In the workflow, you: +To handle these scenarios, you can use the [Login Route](#login-route) to validate that the email and password match the existing identity. If so, you can allow the admin user, for example, to register as a customer. -1. Use the `deleteManagerStep` defined earlier to delete the manager. -2. Retrieve the auth identity of the manager using Query. To do that, you filter the `app_metadata` property of an auth identity, which holds the user's ID under `{actor_type_name}_id`. So, in this case, it's `manager_id`. -3. Check that the auth identity exist, then, update the auth identity to remove the ID of the manager from it. +Otherwise, if the email and password don't match the existing identity, such as when the email belongs to another customer, the [Login Route](#login-route) returns an error: -You can use this workflow when deleting a manager, such as in an API route. +```json +{ + "type": "unauthorized", + "message": "Invalid email or password" +} +``` +You can show that error message to the customer. -# Auth Module Options +*** -In this document, you'll learn about the options of the Auth Module. +## Login Route -## providers +The Medusa application defines an API route at `/auth/{actor_type}/{provider}` that authenticates a user of an actor type. It returns a JWT token that can be passed in [the header of subsequent requests](https://docs.medusajs.com/api/store#authentication) to send authenticated requests. -The `providers` option is an array of auth module providers. +```bash +curl -X POST http://localhost:9000/auth/{actor_type}/{providers} +-H 'Content-Type: application/json' \ +--data-raw '{ + "email": "Whitney_Schultz@gmail.com" + // ... +}' +``` -When the Medusa application starts, these providers are registered and can be used to handle authentication. +For example, if you're authenticating a customer, you send a request to `/auth/customer/emailpass`. -By default, the `emailpass` provider is registered to authenticate customers and admin users. +### Path Parameters -For example: +Its path parameters are: -```ts title="medusa-config.ts" -import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils" +- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. +- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`. -// ... +### Request Body Parameters -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/medusa/auth", - dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER], - options: { - providers: [ - { - resolve: "@medusajs/medusa/auth-emailpass", - id: "emailpass", - options: { - // provider options... - }, - }, - ], - }, - }, - ], -}) -``` +This route accepts in the request body the data that the specified authentication provider requires to handle authentication. -The `providers` option is an array of objects that accept the following properties: +For example, the EmailPass provider requires an `email` and `password` fields in the request body. -- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory. -- `id`: A string indicating the provider's unique name or ID. -- `options`: An optional object of the module provider's options. +#### Overriding Callback URL -*** +For the [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md) and [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) providers, you can pass a `callback_url` body parameter that overrides the `callbackUrl` set in the provider's configurations. -## Auth CORS +This is useful if you want to redirect the user to a different URL after authentication based on their actor type. For example, you can set different `callback_url` for admin users and customers. -The Medusa application's authentication API routes are defined under the `/auth` prefix that requires setting the `authCors` property of the `http` configuration. +### Response Fields -By default, the Medusa application you created will have an `AUTH_CORS` environment variable, which is used as the value of `authCors`. +If the authentication is successful, you'll receive a `token` field in the response body object: -Refer to [Medusa's configuration guide](https://docs.medusajs.com/references/medusa-config#authCors/index.html.md) to learn more about the `authCors` configuration. +```json +{ + "token": "..." +} +``` -*** +Use that token in the header of subsequent requests to send authenticated requests. -## authMethodsPerActor Configuration +If the authentication requires more action with a third-party service, you'll receive a `location` property: -The Medusa application's configuration accept an `authMethodsPerActor` configuration which restricts the allowed auth providers used with an actor type. +```json +{ + "location": "https://..." +} +``` -Learn more about the `authMethodsPerActor` configuration in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers#configure-allowed-auth-providers-of-actor-types/index.html.md). +Redirect to that URL in the frontend to continue the authentication process with the third-party service. +[How to login Customers using the authentication route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/login/index.html.md). -# How to Handle Password Reset Token Event +*** -In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#generate-reset-password-token-route/index.html.md). +## Validate Callback Route -You'll create a subscriber that listens to the event. When the event is emitted, the subscriber sends an email notification to the user. +The Medusa application defines an API route at `/auth/{actor_type}/{provider}/callback` that's useful for validating the authentication callback or redirect from third-party services like Google. -### Prerequisites +```bash +curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/callback?code=123&state=456 +``` -- [A notification provider module, such as SendGrid](https://docs.medusajs.com/architectural-modules/notification/sendgrid/index.html.md) +Refer to the [third-party authentication flow](#2-third-party-service-authenticate-flow) section to see how this route fits into the authentication flow. -## 1. Create Subscriber +### Path Parameters -The first step is to create a subscriber that listens to the `auth.password_reset` and sends the user a notification with instructions to reset their password. +Its path parameters are: -Create the file `src/subscribers/handle-reset.ts` with the following content: +- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. +- `{provider}`: the auth provider to handle the authentication. For example, `google`. -```ts title="src/subscribers/handle-reset.ts" highlights={highlights} collapsibleLines="1-6" expandMoreLabel="Show Imports" -import { - SubscriberArgs, - type SubscriberConfig, -} from "@medusajs/medusa" -import { Modules } from "@medusajs/framework/utils" +### Query Parameters -export default async function resetPasswordTokenHandler({ - event: { data: { - entity_id: email, - token, - actor_type, - } }, - container, -}: SubscriberArgs<{ entity_id: string, token: string, actor_type: string }>) { - const notificationModuleService = container.resolve( - Modules.NOTIFICATION - ) +This route accepts all the query parameters that the third-party service sends to the frontend after the user completes the authentication process, such as the `code` and `state` query parameters. - const urlPrefix = actor_type === "customer" ? - "https://storefront.com" : - "https://admin.com" +### Response Fields - await notificationModuleService.createNotifications({ - to: email, - channel: "email", - template: "reset-password-template", - data: { - // a URL to a frontend application - url: `${urlPrefix}/reset-password?token=${token}&email=${email}`, - }, - }) -} +If the authentication is successful, you'll receive a `token` field in the response body object: -export const config: SubscriberConfig = { - event: "auth.password_reset", +```json +{ + "token": "..." } ``` -You subscribe to the `auth.password_reset` event. The event has a data payload object with the following properties: - -- `entity_id`: The identifier of the user. When using the `emailpass` provider, it's the user's email. -- `token`: The token to reset the user's password. -- `actor_type`: The user's actor type. For example, if the user is a customer, the `actor_type` is `customer`. If it's an admin user, the `actor_type` is `user`. - -This event's payload previously had an `actorType` field. It was renamed to `actor_type` after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7). - -In the subscriber, you: +In your frontend, decode the token using tools like [react-jwt](https://www.npmjs.com/package/react-jwt): -- Decide the frontend URL based on whether the user is a customer or admin user by checking the value of `actor_type`. -- Resolve the Notification Module and use its `createNotifications` method to send the notification. -- You pass to the `createNotifications` method an object having the following properties: - - `to`: The identifier to send the notification to, which in this case is the email. - - `channel`: The channel to send the notification through, which in this case is email. - - `template`: The template ID in the third-party service. - - `data`: The data payload to pass to the template. You pass the URL to redirect the user to. You must pass the token and email in the URL so that the frontend can send them later to the Medusa application when reseting the password. +- If the decoded data has an `actor_id` property, the user is already registered. So, use this token for subsequent authenticated requests. +- If not, use the token in the header of a request that creates the user, such as the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers). *** -## 2. Test it Out: Generate Reset Password Token +## Refresh Token Route -To test the subscriber out, send a request to the `/auth/{actor_type}/{auth_provider}/reset-password` API route, replacing `{actor_type}` and `{auth_provider}` with the user's actor type and provider used for authentication respectively. +The Medusa application defines an API route at `/auth/token/refresh` that's useful after authenticating a user with a third-party service to populate the user's token with their new information. -For example, to generate a reset password token for an admin user using the `emailpass` provider, send the following request: +It requires the user's JWT token that they received from the authentication or callback routes. ```bash -curl --location 'http://localhost:9000/auth/user/emailpass/reset-password' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "identifier": "admin-test@gmail.com" -}' +curl -X POST http://localhost:9000/auth/token/refresh \ +-H 'Authorization: Bearer {token}' ``` -In the request body, you must pass an `identifier` parameter. Its value is the user's identifier, which is the email in this case. +### Response Fields -If the token is generated successfully, the request returns a response with `201` status code. In the terminal, you'll find the following message indicating that the `auth.password_reset` event was emitted and your subscriber ran: +If the token was refreshed successfully, you'll receive a `token` field in the response body object: -```plain -info: Processing auth.password_reset which has 1 subscribers +```json +{ + "token": "..." +} ``` -The notification is sent to the user with the frontend URL to enter a new password. +Use that token in the header of subsequent requests to send authenticated requests. *** -## Next Steps: Implementing Frontend +## Reset Password Routes -In your frontend, you must have a page that accepts `token` and `email` query parameters. +To reset a user's password: -The page shows the user password fields to enter their new password, then submits the new password, token, and email to the [Reset Password Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#reset-password-route/index.html.md). +1. Generate a token using the [Generate Reset Password Token API route](#generate-reset-password-token-route). + - The API route emits the `auth.password_reset` event, passing the token in the payload. + - You can create a subscriber, as seen in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/reset-password/index.html.md), that listens to the event and send a notification to the user. +2. Pass the token to the [Reset Password API route](#reset-password-route) to reset the password. + - The URL in the user's notification should direct them to a frontend URL, which sends a request to this route. -### Examples +[Storefront Development: How to Reset a Customer's Password.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md) -- [Storefront Guide: Reset Customer Password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md) +### Generate Reset Password Token Route +The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/reset-password` that emits the `auth.password_reset` event, passing the token in the payload. -# Customer Accounts +```bash +curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/reset-password +-H 'Content-Type: application/json' \ +--data-raw '{ + "identifier": "Whitney_Schultz@gmail.com" +}' +``` -In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application. +This API route is useful for providers like `emailpass` that store a user's password and use it for authentication. -## `has_account` Property +#### Path Parameters -The [Customer data model](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) has a `has_account` property, which is a boolean that indicates whether a customer is registered. +Its path parameters are: -When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`. +- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. +- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`. -When this or another guest customer registers an account with the same email, a new `Customer` record is created with `has_account` set to `true`. +#### Request Body Parameters -*** +This route accepts in the request body an object having the following property: -## Email Uniqueness +- `identifier`: The user's identifier in the specified auth provider. For example, for the `emailpass` auth provider, you pass the user's email. -The above behavior means that two `Customer` records may exist with the same email. However, the main difference is the `has_account` property's value. +#### Response Fields -So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email. +If the authentication is successful, the request returns a `201` response code. +### Reset Password Route -# Links between Customer Module and Other Modules +The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/update` that accepts a token and, if valid, updates the user's password. -This document showcases the module links defined between the Customer Module and other commerce modules. +```bash +curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/update?token=123 +-H 'Content-Type: application/json' \ +--data-raw '{ + "email": "Whitney_Schultz@gmail.com", + "password": "supersecret" +}' +``` -## Summary +This API route is useful for providers like `emailpass` that store a user's password and use it for logging them in. -The Customer Module has the following links to other modules: +#### Path Parameters -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. +Its path parameters are: -- [`Customer` data model \<> `AccountHolder` data model of Payment Module](#payment-module). -- [`Cart` data model of Cart Module \<> `Customer` data model](#cart-module). (Read-only). -- [`Order` data model of Order Module \<> `Customer` data model](#order-module). (Read-only). +- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. +- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`. -*** +#### Query Parameters -## Payment Module +The route accepts a `token` query parameter, which is the token generated using the [Generate Reset Password Token route](#generate-reset-password-token-route). -Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it. +### Request Body Parameters -This link is available starting from Medusa `v2.5.0`. +This route accepts in the request body an object that has the data necessary for the provider to update the user's password. -### Retrieve with Query +For the `emailpass` provider, you must pass the following properties: -To retrieve the account holder associated with a customer with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`: +- `email`: The user's email. +- `password`: The new password. -### query.graph +### Response Fields -```ts -const { data: customers } = await query.graph({ - entity: "customer", - fields: [ - "account_holder.*", - ], -}) +If the authentication is successful, the request returns an object with a `success` property set to `true`: -// customers.account_holder +```json +{ + "success": "true" +} ``` -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... -const { data: customers } = useQueryGraphStep({ - entity: "customer", - fields: [ - "account_holder.*", - ], -}) +# Auth Providers -// customers.account_holder -``` +In this document, you’ll learn how the Auth Module handles authentication using providers. -### Manage with Link +## What's an Auth Module Provider? -To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +An auth module provider handles authenticating customers and users, either using custom logic or by integrating a third-party service. -### link.create +For example, the EmailPass Auth Module Provider authenticates a user using their email and password, whereas the Google Auth Module Provider authenticates users using their Google account. -```ts -import { Modules } from "@medusajs/framework/utils" +### Auth Providers List -// ... +- [Emailpass](https://docs.medusajs.com/commerce-modules/auth/auth-providers/emailpass/index.html.md) +- [Google](https://docs.medusajs.com/commerce-modules/auth/auth-providers/google/index.html.md) +- [GitHub](https://docs.medusajs.com/commerce-modules/auth/auth-providers/github/index.html.md) -await link.create({ - [Modules.CUSTOMER]: { - customer_id: "cus_123", - }, - [Modules.PAYMENT]: { - account_holder_id: "acchld_123", - }, -}) -``` +*** -### createRemoteLinkStep +## Configure Allowed Auth Providers of Actor Types -```ts -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +By default, users of all actor types can authenticate with all installed auth module providers. -// ... +To restrict the auth providers used for actor types, use the [authMethodsPerActor option](https://docs.medusajs.com/references/medusa-config#http-authMethodsPerActor-1-3/index.html.md) in Medusa's configurations: -createRemoteLinkStep({ - [Modules.CUSTOMER]: { - customer_id: "cus_123", - }, - [Modules.PAYMENT]: { - account_holder_id: "acchld_123", +```ts title="medusa-config.ts" +module.exports = defineConfig({ + projectConfig: { + http: { + authMethodsPerActor: { + user: ["google"], + customer: ["emailpass"], + }, + // ... + }, + // ... }, }) ``` +When you specify the `authMethodsPerActor` configuration, it overrides the default. So, if you don't specify any providers for an actor type, users of that actor type can't authenticate with any provider. + *** -## Cart Module +## How to Create an Auth Module Provider -Medusa defines a read-only link between the `Customer` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a customer's carts, but you don't manage the links in a pivot table in the database. The customer of a cart is determined by the `customer_id` property of the `Cart` data model. +Refer to [this guide](https://docs.medusajs.com/references/auth/provider/index.html.md) to learn how to create an auth module provider. -### Retrieve with Query -To retrieve a customer's carts with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: +# How to Create an Actor Type -### query.graph +In this document, learn how to create an actor type and authenticate its associated data model. -```ts -const { data: customers } = await query.graph({ - entity: "customer", - fields: [ - "carts.*", - ], -}) +## 0. Create Module with Data Model -// customers.carts -``` +Before creating an actor type, you must have a module with a data model representing the actor type. -### useQueryGraphStep +Learn how to create a module in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md). -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +The rest of this guide uses this `Manager` data model as an example: -// ... +```ts title="src/modules/manager/models/manager.ts" +import { model } from "@medusajs/framework/utils" -const { data: customers } = useQueryGraphStep({ - entity: "customer", - fields: [ - "carts.*", - ], +const Manager = model.define("manager", { + id: model.id().primaryKey(), + firstName: model.text(), + lastName: model.text(), + email: model.text(), }) -// customers.carts +export default Manager ``` *** -## Order Module - -Medusa defines a read-only link between the `Customer` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model. This means you can retrieve the details of a customer's orders, but you don't manage the links in a pivot table in the database. The customer of an order is determined by the `customer_id` property of the `Order` data model. - -### Retrieve with Query - -To retrieve a customer's orders with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: +## 1. Create Workflow -### query.graph +Start by creating a workflow that does two things: -```ts -const { data: customers } = await query.graph({ - entity: "customer", - fields: [ - "orders.*", - ], -}) +- Creates a record of the `Manager` data model. +- Sets the `app_metadata` property of the associated `AuthIdentity` record based on the new actor type. -// customers.orders -``` +For example, create the file `src/workflows/create-manager.ts`. with the following content: -### useQueryGraphStep +```ts title="src/workflows/create-manager.ts" highlights={workflowHighlights} +import { + createWorkflow, + createStep, + StepResponse, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" +import { + setAuthAppMetadataStep, +} from "@medusajs/medusa/core-flows" +import ManagerModuleService from "../modules/manager/service" -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +type CreateManagerWorkflowInput = { + manager: { + first_name: string + last_name: string + email: string + } + authIdentityId: string +} -// ... +const createManagerStep = createStep( + "create-manager-step", + async ({ + manager: managerData, + }: Pick, + { container }) => { + const managerModuleService: ManagerModuleService = + container.resolve("managerModuleService") -const { data: customers } = useQueryGraphStep({ - entity: "customer", - fields: [ - "orders.*", - ], -}) + const manager = await managerModuleService.createManager( + managerData + ) -// customers.orders -``` + return new StepResponse(manager) + } +) +const createManagerWorkflow = createWorkflow( + "create-manager", + function (input: CreateManagerWorkflowInput) { + const manager = createManagerStep({ + manager: input.manager, + }) -# Links between Currency Module and Other Modules + setAuthAppMetadataStep({ + authIdentityId: input.authIdentityId, + actorType: "manager", + value: manager.id, + }) -This document showcases the module links defined between the Currency Module and other commerce modules. + return new WorkflowResponse(manager) + } +) -## Summary +export default createManagerWorkflow +``` -The Currency Module has the following links to other modules: +This workflow accepts the manager’s data and the associated auth identity’s ID as inputs. The next sections explain how the auth identity ID is retrieved. -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. +The workflow has two steps: -- [`Currency` data model of Store Module \<> `Currency` data model of Currency Module](#store-module). (Read-only). +1. Create the manager using the `createManagerStep`. +2. Set the `app_metadata` property of the associated auth identity using the `setAuthAppMetadataStep` from Medusa's core workflows. You specify the actor type `manager` in the `actorType` property of the step’s input. *** -## Store Module +## 2. Define the Create API Route -The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol. +Next, you’ll use the workflow defined in the previous section in an API route that creates a manager. -Instead, Medusa defines a read-only link between the Currency Module's `Currency` data model and the [Store Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/store/index.html.md)'s `Currency` data model. This means you can retrieve the details of a store's supported currencies, but you don't manage the links in a pivot table in the database. The currencies of a store are determined by the `currency_code` of the `Currency` data model in the Store Module. +So, create the file `src/api/manager/route.ts` with the following content: -### Retrieve with Query +```ts title="src/api/manager/route.ts" highlights={createRouteHighlights} +import type { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import { MedusaError } from "@medusajs/framework/utils" +import createManagerWorkflow from "../../workflows/create-manager" -To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`: +type RequestBody = { + first_name: string + last_name: string + email: string +} -### query.graph +export async function POST( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) { + // If `actor_id` is present, the request carries + // authentication for an existing manager + if (req.auth_context.actor_id) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Request already authenticated as a manager." + ) + } -```ts -const { data: stores } = await query.graph({ - entity: "store", - fields: [ - "supported_currencies.currency.*", - ], -}) - -// stores.supported_currencies -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... - -const { data: stores } = useQueryGraphStep({ - entity: "store", - fields: [ - "supported_currencies.currency.*", - ], -}) - -// stores.supported_currencies + const { result } = await createManagerWorkflow(req.scope) + .run({ + input: { + manager: req.body, + authIdentityId: req.auth_context.auth_identity_id, + }, + }) + + res.status(200).json({ manager: result }) +} ``` +Since the manager must be associated with an `AuthIdentity` record, the request is expected to be authenticated, even if the manager isn’t created yet. This can be achieved by: -# Cart Concepts - -In this document, you’ll get an overview of the main concepts of a cart. - -## Shipping and Billing Addresses - -A cart has a shipping and billing address. Both of these addresses are represented by the [Address data model](https://docs.medusajs.com/references/cart/models/Address/index.html.md). - -![A diagram showcasing the relation between the Cart and Address data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1711532392/Medusa%20Resources/cart-addresses_ls6qmv.jpg) - -*** - -## Line Items - -A line item, represented by the [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model, is a quantity of a product variant added to the cart. A cart has multiple line items. - -A line item stores some of the product variant’s properties, such as the `product_title` and `product_description`. It also stores data related to the item’s quantity and price. +1. Obtaining a token usng the [/auth route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md). +2. Passing the token in the bearer header of the request to this route. -In the Medusa application, a product variant is implemented in the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md). +In the API route, you create the manager using the workflow from the previous section and return it in the response. *** -## Shipping Methods +## 3. Apply the `authenticate` Middleware -A shipping method, represented by the [ShippingMethod data model](https://docs.medusajs.com/references/cart/models/ShippingMethod/index.html.md), is used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method. +The last step is to apply the `authenticate` middleware on the API routes that require a manager’s authentication. -In the Medusa application, the shipping method is created from a shipping option, available through the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md). Its ID is stored in the `shipping_option_id` property of the method. +To do that, create the file `src/api/middlewares.ts` with the following content: -### data Property +```ts title="src/api/middlewares.ts" highlights={middlewareHighlights} +import { + defineMiddlewares, + authenticate, +} from "@medusajs/framework/http" -After an order is placed, you can use a third-party fulfillment provider to fulfill its shipments. +export default defineMiddlewares({ + routes: [ + { + matcher: "/manager", + method: "POST", + middlewares: [ + authenticate("manager", ["session", "bearer"], { + allowUnregistered: true, + }), + ], + }, + { + matcher: "/manager/me*", + middlewares: [ + authenticate("manager", ["session", "bearer"]), + ], + }, + ], +}) +``` -If the fulfillment provider requires additional custom data to be passed along from the checkout process, set this data in the `ShippingMethod`'s `data` property. +This applies middlewares on two route patterns: -The `data` property is an object used to store custom data relevant later for fulfillment. +1. The `authenticate` middleware is applied on the `/manager` API route for `POST` requests while allowing unregistered managers. This requires that a bearer token be passed in the request to access the manager’s auth identity but doesn’t require the manager to be registered. +2. The `authenticate` middleware is applied on all routes starting with `/manager/me`, restricting these routes to authenticated managers only. +### Retrieve Manager API Route -# Links between Cart Module and Other Modules +For example, create the file `src/api/manager/me/route.ts` with the following content: -This document showcases the module links defined between the Cart Module and other commerce modules. +```ts title="src/api/manager/me/route.ts" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" +import ManagerModuleService from "../../../modules/manager/service" -## Summary +export async function GET( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +): Promise { + const managerModuleService: ManagerModuleService = + req.scope.resolve("managerModuleService") -The Cart Module has the following links to other modules: + const manager = await managerModuleService.retrieveManager( + req.auth_context.actor_id + ) -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. + res.json({ manager }) +} +``` -- [`Cart` data model \<> `Customer` data model of Customer Module](#customer-module). (Read-only). -- [`Order` data model of Order Module \<> `Cart` data model](#order-module). -- [`Cart` data model \<> `PaymentCollection` data model of Payment Module](#payment-module). -- [`LineItem` data model \<> `Product` data model of Product Module](#product-module). (Read-only). -- [`LineItem` data model \<> `ProductVariant` data model of Product Module](#product-module). (Read-only). -- [`Cart` data model \<> `Promotion` data model of Promotion Module](#promotion-module). -- [`Cart` data model \<> `Region` data model of Region Module](#region-module). (Read-only). -- [`Cart` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module). (Read-only). +This route is only accessible by authenticated managers. You access the manager’s ID using `req.auth_context.actor_id`. *** -## Customer Module +## Test Custom Actor Type Authentication Flow -Medusa defines a read-only link between the `Cart` data model and the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md)'s `Customer` data model. This means you can retrieve the details of a cart's customer, but you don't manage the links in a pivot table in the database. The customer of a cart is determined by the `customer_id` property of the `Cart` data model. +To authenticate managers: -### Retrieve with Query +1. Send a `POST` request to `/auth/manager/emailpass/register` to create an auth identity for the manager: -To retrieve the customer of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`: +```bash +curl -X POST 'http://localhost:9000/auth/manager/emailpass/register' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "email": "manager@gmail.com", + "password": "supersecret" +}' +``` -### query.graph +Copy the returned token to use it in the next request. -```ts -const { data: carts } = await query.graph({ - entity: "cart", - fields: [ - "customer.*", - ], -}) +2. Send a `POST` request to `/manager` to create a manager: -// carts.order +```bash +curl -X POST 'http://localhost:9000/manager' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer {token}' \ +--data-raw '{ + "first_name": "John", + "last_name": "Doe", + "email": "manager@gmail.com" +}' ``` -### useQueryGraphStep +Replace `{token}` with the token returned in the previous step. -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +3. Send a `POST` request to `/auth/manager/emailpass` again to retrieve an authenticated token for the manager: -// ... +```bash +curl -X POST 'http://localhost:9000/auth/manager/emailpass' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "email": "manager@gmail.com", + "password": "supersecret" +}' +``` -const { data: carts } = useQueryGraphStep({ - entity: "cart", - fields: [ - "customer.*", - ], -}) +4. You can now send authenticated requests as a manager. For example, send a `GET` request to `/manager/me` to retrieve the authenticated manager’s details: -// carts.order +```bash +curl 'http://localhost:9000/manager/me' \ +-H 'Authorization: Bearer {token}' ``` +Whenever you want to log in as a manager, use the `/auth/manager/emailpass` API route, as explained in step 3. + *** -## Order Module +## Delete User of Actor Type -The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management features. +When you delete a user of the actor type, you must update its auth identity to remove the association to the user. -Medusa defines a link between the `Cart` and `Order` data models. The cart is linked to the order created once the cart is completed. +For example, create the following workflow that deletes a manager and updates its auth identity, create the file `src/workflows/delete-manager.ts` with the following content: -![A diagram showcasing an example of how data models from the Cart and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728375735/Medusa%20Resources/cart-order_ijwmfs.jpg) +```ts title="src/workflows/delete-manager.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports" +import { + createStep, + StepResponse, +} from "@medusajs/framework/workflows-sdk" +import ManagerModuleService from "../modules/manager/service" -### Retrieve with Query +export type DeleteManagerWorkflow = { + id: string +} -To retrieve the order of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`: +const deleteManagerStep = createStep( + "delete-manager-step", + async ( + { id }: DeleteManagerWorkflow, + { container }) => { + const managerModuleService: ManagerModuleService = + container.resolve("managerModuleService") -### query.graph + const manager = await managerModuleService.retrieve(id) -```ts -const { data: carts } = await query.graph({ - entity: "cart", - fields: [ - "order.*", - ], -}) + await managerModuleService.deleteManagers(id) -// carts.order + return new StepResponse(undefined, { manager }) + }, + async ({ manager }, { container }) => { + const managerModuleService: ManagerModuleService = + container.resolve("managerModuleService") + + await managerModuleService.createManagers(manager) + } + ) ``` -### useQueryGraphStep +You add a step that deletes the manager using the `deleteManagers` method of the module's main service. In the compensation function, you create the manager again. -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +Next, in the same file, add the workflow that deletes a manager: + +```ts title="src/workflows/delete-manager.ts" collapsibleLines="1-15" expandButtonLabel="Show Imports" highlights={deleteHighlights} +// other imports +import { MedusaError } from "@medusajs/framework/utils" +import { + WorkflowData, + WorkflowResponse, + createWorkflow, + transform, +} from "@medusajs/framework/workflows-sdk" +import { + setAuthAppMetadataStep, + useQueryGraphStep, +} from "@medusajs/medusa/core-flows" // ... -const { data: carts } = useQueryGraphStep({ - entity: "cart", - fields: [ - "order.*", - ], -}) - -// carts.order -``` +export const deleteManagerWorkflow = createWorkflow( + "delete-manager", + ( + input: WorkflowData + ): WorkflowResponse => { + deleteManagerStep(input) -### Manage with Link + const { data: authIdentities } = useQueryGraphStep({ + entity: "auth_identity", + fields: ["id"], + filters: { + app_metadata: { + // the ID is of the format `{actor_type}_id`. + manager_id: input.id, + }, + }, + }) -To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): + const authIdentity = transform( + { authIdentities }, + ({ authIdentities }) => { + const authIdentity = authIdentities[0] -### link.create + if (!authIdentity) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + "Auth identity not found" + ) + } -```ts -import { Modules } from "@medusajs/framework/utils" + return authIdentity + } + ) -// ... + setAuthAppMetadataStep({ + authIdentityId: authIdentity.id, + actorType: "manager", + value: null, + }) -await link.create({ - [Modules.CART]: { - cart_id: "cart_123", - }, - [Modules.ORDER]: { - order_id: "order_123", - }, -}) + return new WorkflowResponse(input.id) + } +) ``` -### createRemoteLinkStep +In the workflow, you: -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +1. Use the `deleteManagerStep` defined earlier to delete the manager. +2. Retrieve the auth identity of the manager using Query. To do that, you filter the `app_metadata` property of an auth identity, which holds the user's ID under `{actor_type_name}_id`. So, in this case, it's `manager_id`. +3. Check that the auth identity exist, then, update the auth identity to remove the ID of the manager from it. -// ... +You can use this workflow when deleting a manager, such as in an API route. -createRemoteLinkStep({ - [Modules.CART]: { - cart_id: "cart_123", - }, - [Modules.ORDER]: { - order_id: "order_123", - }, -}) -``` -*** +# Auth Module Options -## Payment Module +In this document, you'll learn about the options of the Auth Module. -The [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md) handles payment processing and management. +## providers -Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart. +The `providers` option is an array of auth module providers. -![A diagram showcasing an example of how data models from the Cart and Payment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711537849/Medusa%20Resources/cart-payment_ixziqm.jpg) +When the Medusa application starts, these providers are registered and can be used to handle authentication. -### Retrieve with Query +By default, the `emailpass` provider is registered to authenticate customers and admin users. -To retrieve the payment collection of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_collection.*` in `fields`: +For example: -### query.graph +```ts title="medusa-config.ts" +import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils" -```ts -const { data: carts } = await query.graph({ - entity: "cart", - fields: [ - "payment_collection.*", +// ... + +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/auth", + dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER], + options: { + providers: [ + { + resolve: "@medusajs/medusa/auth-emailpass", + id: "emailpass", + options: { + // provider options... + }, + }, + ], + }, + }, ], }) - -// carts.payment_collection ``` -### useQueryGraphStep +The `providers` option is an array of objects that accept the following properties: -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory. +- `id`: A string indicating the provider's unique name or ID. +- `options`: An optional object of the module provider's options. -// ... +*** -const { data: carts } = useQueryGraphStep({ - entity: "cart", - fields: [ - "payment_collection.*", - ], -}) +## Auth CORS -// carts.payment_collection -``` +The Medusa application's authentication API routes are defined under the `/auth` prefix that requires setting the `authCors` property of the `http` configuration. -### Manage with Link +By default, the Medusa application you created will have an `AUTH_CORS` environment variable, which is used as the value of `authCors`. -To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +Refer to [Medusa's configuration guide](https://docs.medusajs.com/references/medusa-config#authCors/index.html.md) to learn more about the `authCors` configuration. -### link.create +*** -```ts -import { Modules } from "@medusajs/framework/utils" +## authMethodsPerActor Configuration -// ... +The Medusa application's configuration accept an `authMethodsPerActor` configuration which restricts the allowed auth providers used with an actor type. -await link.create({ - [Modules.CART]: { - cart_id: "cart_123", - }, - [Modules.PAYMENT]: { - payment_collection_id: "paycol_123", - }, -}) -``` +Learn more about the `authMethodsPerActor` configuration in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers#configure-allowed-auth-providers-of-actor-types/index.html.md). -### createRemoteLinkStep -```ts -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +# How to Handle Password Reset Token Event -// ... +In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#generate-reset-password-token-route/index.html.md). -createRemoteLinkStep({ - [Modules.CART]: { - cart_id: "cart_123", - }, - [Modules.PAYMENT]: { - payment_collection_id: "paycol_123", - }, -}) -``` +You'll create a subscriber that listens to the event. When the event is emitted, the subscriber sends an email notification to the user. -*** +### Prerequisites -## Product Module +- [A notification provider module, such as SendGrid](https://docs.medusajs.com/architectural-modules/notification/sendgrid/index.html.md) -Medusa defines read-only links between: +## 1. Create Subscriber -- the `LineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `Product` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `LineItem` data model. -- the `LineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `ProductVariant` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `LineItem` data model. +The first step is to create a subscriber that listens to the `auth.password_reset` and sends the user a notification with instructions to reset their password. -### Retrieve with Query +Create the file `src/subscribers/handle-reset.ts` with the following content: -To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`: +```ts title="src/subscribers/handle-reset.ts" highlights={highlights} collapsibleLines="1-6" expandMoreLabel="Show Imports" +import { + SubscriberArgs, + type SubscriberConfig, +} from "@medusajs/medusa" +import { Modules } from "@medusajs/framework/utils" -To retrieve the product, pass `product.*` in `fields`. +export default async function resetPasswordTokenHandler({ + event: { data: { + entity_id: email, + token, + actor_type, + } }, + container, +}: SubscriberArgs<{ entity_id: string, token: string, actor_type: string }>) { + const notificationModuleService = container.resolve( + Modules.NOTIFICATION + ) -### query.graph + const urlPrefix = actor_type === "customer" ? + "https://storefront.com" : + "https://admin.com" -```ts -const { data: lineItems } = await query.graph({ - entity: "line_item", - fields: [ - "variant.*", - ], -}) + await notificationModuleService.createNotifications({ + to: email, + channel: "email", + template: "reset-password-template", + data: { + // a URL to a frontend application + url: `${urlPrefix}/reset-password?token=${token}&email=${email}`, + }, + }) +} -// lineItems.variant +export const config: SubscriberConfig = { + event: "auth.password_reset", +} ``` -### useQueryGraphStep +You subscribe to the `auth.password_reset` event. The event has a data payload object with the following properties: -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +- `entity_id`: The identifier of the user. When using the `emailpass` provider, it's the user's email. +- `token`: The token to reset the user's password. +- `actor_type`: The user's actor type. For example, if the user is a customer, the `actor_type` is `customer`. If it's an admin user, the `actor_type` is `user`. -// ... +This event's payload previously had an `actorType` field. It was renamed to `actor_type` after [Medusa v2.0.7](https://github.com/medusajs/medusa/releases/tag/v2.0.7). -const { data: lineItems } = useQueryGraphStep({ - entity: "line_item", - fields: [ - "variant.*", - ], -}) +In the subscriber, you: -// lineItems.variant -``` +- Decide the frontend URL based on whether the user is a customer or admin user by checking the value of `actor_type`. +- Resolve the Notification Module and use its `createNotifications` method to send the notification. +- You pass to the `createNotifications` method an object having the following properties: + - `to`: The identifier to send the notification to, which in this case is the email. + - `channel`: The channel to send the notification through, which in this case is email. + - `template`: The template ID in the third-party service. + - `data`: The data payload to pass to the template. You pass the URL to redirect the user to. You must pass the token and email in the URL so that the frontend can send them later to the Medusa application when reseting the password. *** -## Promotion Module - -The [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md) provides discount features. - -Medusa defines a link between the `Cart` and `Promotion` data models. This indicates the promotions applied on a cart. - -![A diagram showcasing an example of how data models from the Cart and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711538015/Medusa%20Resources/cart-promotion_kuh9vm.jpg) - -Medusa also defines a read-only link between the `LineItemAdjustment` and `Promotion` data models. This means you can retrieve the details of the promotion applied on a line item, but you don't manage the links in a pivot table in the database. The promotion of a line item is determined by the `promotion_id` property of the `LineItemAdjustment` data model. - -### Retrieve with Query - -To retrieve the promotions of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `promotions.*` in `fields`: - -To retrieve the promotion of a line item adjustment, pass `promotion.*` in `fields`. - -### query.graph - -```ts -const { data: carts } = await query.graph({ - entity: "cart", - fields: [ - "promotions.*", - ], -}) - -// carts.promotions -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +## 2. Test it Out: Generate Reset Password Token -// ... +To test the subscriber out, send a request to the `/auth/{actor_type}/{auth_provider}/reset-password` API route, replacing `{actor_type}` and `{auth_provider}` with the user's actor type and provider used for authentication respectively. -const { data: carts } = useQueryGraphStep({ - entity: "cart", - fields: [ - "promotions.*", - ], -}) +For example, to generate a reset password token for an admin user using the `emailpass` provider, send the following request: -// carts.promotions +```bash +curl --location 'http://localhost:9000/auth/user/emailpass/reset-password' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "identifier": "admin-test@gmail.com" +}' ``` -### Manage with Link - -To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): - -### link.create - -```ts -import { Modules } from "@medusajs/framework/utils" +In the request body, you must pass an `identifier` parameter. Its value is the user's identifier, which is the email in this case. -// ... +If the token is generated successfully, the request returns a response with `201` status code. In the terminal, you'll find the following message indicating that the `auth.password_reset` event was emitted and your subscriber ran: -await link.create({ - [Modules.CART]: { - cart_id: "cart_123", - }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", - }, -}) +```plain +info: Processing auth.password_reset which has 1 subscribers ``` -### createRemoteLinkStep - -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" - -// ... - -createRemoteLinkStep({ - [Modules.CART]: { - cart_id: "cart_123", - }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", - }, -}) -``` +The notification is sent to the user with the frontend URL to enter a new password. *** -## Region Module +## Next Steps: Implementing Frontend -Medusa defines a read-only link between the `Cart` data model and the [Region Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/region/index.html.md)'s `Region` data model. This means you can retrieve the details of a cart's region, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model. +In your frontend, you must have a page that accepts `token` and `email` query parameters. -### Retrieve with Query +The page shows the user password fields to enter their new password, then submits the new password, token, and email to the [Reset Password Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#reset-password-route/index.html.md). -To retrieve the region of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`: +### Examples -### query.graph +- [Storefront Guide: Reset Customer Password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md) -```ts -const { data: carts } = await query.graph({ - entity: "cart", - fields: [ - "region.*", - ], -}) -// carts.region -``` +# Links between Currency Module and Other Modules -### useQueryGraphStep +This document showcases the module links defined between the Currency Module and other commerce modules. -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +## Summary -// ... +The Currency Module has the following links to other modules: -const { data: carts } = useQueryGraphStep({ - entity: "cart", - fields: [ - "region.*", - ], -}) +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. -// carts.region -``` +- [`Currency` data model of Store Module \<> `Currency` data model of Currency Module](#store-module). (Read-only). *** -## Sales Channel Module +## Store Module -Medusa defines a read-only link between the `Cart` data model and the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md)'s `SalesChannel` data model. This means you can retrieve the details of a cart's sales channel, but you don't manage the links in a pivot table in the database. The sales channel of a cart is determined by the `sales_channel_id` property of the `Cart` data model. +The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol. + +Instead, Medusa defines a read-only link between the Currency Module's `Currency` data model and the [Store Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/store/index.html.md)'s `Currency` data model. This means you can retrieve the details of a store's supported currencies, but you don't manage the links in a pivot table in the database. The currencies of a store are determined by the `currency_code` of the `Currency` data model in the Store Module. ### Retrieve with Query -To retrieve the sales channel of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`: +To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`: ### query.graph ```ts -const { data: carts } = await query.graph({ - entity: "cart", +const { data: stores } = await query.graph({ + entity: "store", fields: [ - "sales_channel.*", + "supported_currencies.currency.*", ], }) -// carts.sales_channel +// stores.supported_currencies ``` ### useQueryGraphStep @@ -18683,90 +18556,52 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: carts } = useQueryGraphStep({ - entity: "cart", +const { data: stores } = useQueryGraphStep({ + entity: "store", fields: [ - "sales_channel.*", + "supported_currencies.currency.*", ], }) -// carts.sales_channel +// stores.supported_currencies ``` -# Tax Lines in Cart Module +# Cart Concepts -In this document, you’ll learn about tax lines in a cart and how to retrieve tax lines with the Tax Module. +In this document, you’ll get an overview of the main concepts of a cart. -## What are Tax Lines? +## Shipping and Billing Addresses -A tax line indicates the tax rate of a line item or a shipping method. The [LineItemTaxLine data model](https://docs.medusajs.com/references/cart/models/LineItemTaxLine/index.html.md) represents a line item’s tax line, and the [ShippingMethodTaxLine data model](https://docs.medusajs.com/references/cart/models/ShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line. +A cart has a shipping and billing address. Both of these addresses are represented by the [Address data model](https://docs.medusajs.com/references/cart/models/Address/index.html.md). -![A diagram showcasing the relation between other data models and the tax line models](https://res.cloudinary.com/dza7lstvk/image/upload/v1711534431/Medusa%20Resources/cart-tax-lines_oheaq6.jpg) +![A diagram showcasing the relation between the Cart and Address data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1711532392/Medusa%20Resources/cart-addresses_ls6qmv.jpg) *** -## Tax Inclusivity - -By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount, and then adding them to the item/method’s subtotal. - -However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes. - -So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal. +## Line Items -The following diagram is a simplified showcase of how a subtotal is calculated from the taxes perspective. +A line item, represented by the [LineItem](https://docs.medusajs.com/references/cart/models/LineItem/index.html.md) data model, is a quantity of a product variant added to the cart. A cart has multiple line items. -![A diagram showing an example of calculating the subtotal of a line item using its taxes](https://res.cloudinary.com/dza7lstvk/image/upload/v1711535295/Medusa%20Resources/cart-tax-inclusive_shpr3t.jpg) +A line item stores some of the product variant’s properties, such as the `product_title` and `product_description`. It also stores data related to the item’s quantity and price. -For example, if a line item's amount is `5000`, the tax rate is `10`, and tax inclusivity is enabled, the tax amount is 10% of `5000`, which is `500`, making the unit price of the line item `4500`. +In the Medusa application, a product variant is implemented in the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md). *** -## Retrieve Tax Lines +## Shipping Methods -When using the Cart and Tax modules together, you can use the `getTaxLines` method of the Tax Module’s main service. It retrieves the tax lines for a cart’s line items and shipping methods. +A shipping method, represented by the [ShippingMethod data model](https://docs.medusajs.com/references/cart/models/ShippingMethod/index.html.md), is used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method. -```ts -// retrieve the cart -const cart = await cartModuleService.retrieveCart("cart_123", { - relations: [ - "items.tax_lines", - "shipping_methods.tax_lines", - "shipping_address", - ], -}) +In the Medusa application, the shipping method is created from a shipping option, available through the [Fulfillment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/index.html.md). Its ID is stored in the `shipping_option_id` property of the method. -// retrieve the tax lines -const taxLines = await taxModuleService.getTaxLines( - [ - ...(cart.items as TaxableItemDTO[]), - ...(cart.shipping_methods as TaxableShippingDTO[]), - ], - { - address: { - ...cart.shipping_address, - country_code: - cart.shipping_address.country_code || "us", - }, - } -) -``` +### data Property -Then, use the returned tax lines to set the line items and shipping methods’ tax lines: +After an order is placed, you can use a third-party fulfillment provider to fulfill its shipments. -```ts -// set line item tax lines -await cartModuleService.setLineItemTaxLines( - cart.id, - taxLines.filter((line) => "line_item_id" in line) -) +If the fulfillment provider requires additional custom data to be passed along from the checkout process, set this data in the `ShippingMethod`'s `data` property. -// set shipping method tax lines -await cartModuleService.setLineItemTaxLines( - cart.id, - taxLines.filter((line) => "shipping_line_id" in line) -) -``` +The `data` property is an object used to store custom data relevant later for fulfillment. # Promotions Adjustments in Carts @@ -18887,221 +18722,90 @@ await cartModuleService.setShippingMethodAdjustments( ``` -# Fulfillment Concepts +# Links between Cart Module and Other Modules -In this document, you’ll learn about some basic fulfillment concepts. +This document showcases the module links defined between the Cart Module and other commerce modules. -## Fulfillment Set +## Summary -A fulfillment set is a general form or way of fulfillment. For example, shipping is a form of fulfillment, and pick-up is another form of fulfillment. Each of these can be created as fulfillment sets. +The Cart Module has the following links to other modules: -A fulfillment set is represented by the [FulfillmentSet data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentSet/index.html.md). All other configurations, options, and management features are related to a fulfillment set, in one way or another. +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. -```ts -const fulfillmentSets = await fulfillmentModuleService.createFulfillmentSets( - [ - { - name: "Shipping", - type: "shipping", - }, - { - name: "Pick-up", - type: "pick-up", - }, - ] -) -``` +- [`Cart` data model \<> `Customer` data model of Customer Module](#customer-module). (Read-only). +- [`Order` data model of Order Module \<> `Cart` data model](#order-module). +- [`Cart` data model \<> `PaymentCollection` data model of Payment Module](#payment-module). +- [`LineItem` data model \<> `Product` data model of Product Module](#product-module). (Read-only). +- [`LineItem` data model \<> `ProductVariant` data model of Product Module](#product-module). (Read-only). +- [`Cart` data model \<> `Promotion` data model of Promotion Module](#promotion-module). +- [`Cart` data model \<> `Region` data model of Region Module](#region-module). (Read-only). +- [`Cart` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module). (Read-only). *** -## Service Zone +## Customer Module -A service zone is a collection of geographical zones or areas. It’s used to restrict available shipping options to a defined set of locations. +Medusa defines a read-only link between the `Cart` data model and the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md)'s `Customer` data model. This means you can retrieve the details of a cart's customer, but you don't manage the links in a pivot table in the database. The customer of a cart is determined by the `customer_id` property of the `Cart` data model. -A service zone is represented by the [ServiceZone data model](https://docs.medusajs.com/references/fulfillment/models/ServiceZone/index.html.md). It’s associated with a fulfillment set, as each service zone is specific to a form of fulfillment. For example, if a customer chooses to pick up items, you can restrict the available shipping options based on their location. - -![A diagram showcasing the relation between fulfillment sets, service zones, and geo zones](https://res.cloudinary.com/dza7lstvk/image/upload/v1712329770/Medusa%20Resources/service-zone_awmvfs.jpg) - -A service zone can have multiple geographical zones, each represented by the [GeoZone data model](https://docs.medusajs.com/references/fulfillment/models/GeoZone/index.html.md). It holds location-related details to narrow down supported areas, such as country, city, or province code. - -*** - -## Shipping Profile - -A shipping profile defines a type of items that are shipped in a similar manner. For example, a `default` shipping profile is used for all item types, but the `digital` shipping profile is used for digital items that aren’t shipped and delivered conventionally. - -A shipping profile is represented by the [ShippingProfile data model](https://docs.medusajs.com/references/fulfillment/models/ShippingProfile/index.html.md). It only defines the profile’s details, but it’s associated with the shipping options available for the item type. - - -# Fulfillment Module Provider - -In this document, you’ll learn what a fulfillment module provider is. - -## What’s a Fulfillment Module Provider? - -A fulfillment module provider handles fulfilling items, typically using a third-party integration. - -Fulfillment module providers registered in the Fulfillment Module's [options](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) are stored and represented by the [FulfillmentProvider data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentProvider/index.html.md). - -*** - -## Configure Fulfillment Providers - -The Fulfillment Module accepts a `providers` option that allows you to register providers in your application. - -Learn more about the `providers` option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md). - -*** - -## How to Create a Fulfillment Provider? - -Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider. - - -# Item Fulfillment - -In this document, you’ll learn about the concepts of item fulfillment. - -## Fulfillment Data Model - -A fulfillment is the shipping and delivery of one or more items to the customer. It’s represented by the [Fulfillment data model](https://docs.medusajs.com/references/fulfillment/models/Fulfillment/index.html.md). - -*** - -## Fulfillment Processing by a Fulfillment Provider - -A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items. - -The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped. - -![A diagram showcasing the relation between a fulfillment, fulfillment provider, and shipping option](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331947/Medusa%20Resources/fulfillment-shipping-option_jk9ndp.jpg) - -*** - -## data Property - -The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment. - -For example, the `data` property can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details. - -*** - -## Fulfillment Items - -A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model. - -The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill. - -![A diagram showcasing the relation between fulfillment and fulfillment items.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712332114/Medusa%20Resources/fulfillment-item_etzxb0.jpg) - -*** - -## Fulfillment Label - -Once a shipment is created for the fulfillment, you can store its tracking number, URL, or other related details as a label, represented by the `FulfillmentLabel` data model. - -*** - -## Fulfillment Status - -The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment: - -- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed. -- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped. -- `delivered_at`: The date the fulfillment was delivered. If set, then the fulfillment has been delivered. - - -# Fulfillment Module Options +### Retrieve with Query -In this document, you'll learn about the options of the Fulfillment Module. +To retrieve the customer of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`: -## providers +### query.graph -The `providers` option is an array of fulfillment module providers. +```ts +const { data: carts } = await query.graph({ + entity: "cart", + fields: [ + "customer.*", + ], +}) -When the Medusa application starts, these providers are registered and can be used to process fulfillments. +// carts.order +``` -For example: +### useQueryGraphStep -```ts title="medusa-config.ts" -import { Modules } from "@medusajs/framework/utils" +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/medusa/fulfillment", - options: { - providers: [ - { - resolve: `@medusajs/medusa/fulfillment-manual`, - id: "manual", - options: { - // provider options... - }, - }, - ], - }, - }, +const { data: carts } = useQueryGraphStep({ + entity: "cart", + fields: [ + "customer.*", ], }) -``` - -The `providers` option is an array of objects that accept the following properties: - -- `resolve`: A string indicating either the package name of the module provider or the path to it relative to the `src` directory. -- `id`: A string indicating the provider's unique name or ID. -- `options`: An optional object of the module provider's options. - -# Links between Fulfillment Module and Other Modules - -This document showcases the module links defined between the Fulfillment Module and other commerce modules. - -## Summary - -The Fulfillment Module has the following links to other modules: - -- [`Order` data model of the Order Module \<> `Fulfillment` data model](#order-module). -- [`Return` data model of the Order Module \<> `Fulfillment` data model](#order-module). -- [`PriceSet` data model of the Pricing Module \<> `ShippingOption` data model](#pricing-module). -- [`Product` data model of the Product Module \<> `ShippingProfile` data model](#product-module). -- [`StockLocation` data model of the Stock Location Module \<> `FulfillmentProvider` data model](#stock-location-module). -- [`StockLocation` data model of the Stock Location Module \<> `FulfillmentSet` data model](#stock-location-module). +// carts.order +``` *** ## Order Module -The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management functionalities. - -Medusa defines a link between the `Fulfillment` and `Order` data models. A fulfillment is created for an orders' items. - -![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716549903/Medusa%20Resources/order-fulfillment_h0vlps.jpg) +The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management features. -A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models. +Medusa defines a link between the `Cart` and `Order` data models. The cart is linked to the order created once the cart is completed. -![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399052/Medusa%20Resources/Social_Media_Graphics_2024_Order_Return_vetimk.jpg) +![A diagram showcasing an example of how data models from the Cart and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728375735/Medusa%20Resources/cart-order_ijwmfs.jpg) ### Retrieve with Query -To retrieve the order of a fulfillment with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`: - -To retrieve the return, pass `return.*` in `fields`. +To retrieve the order of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`: ### query.graph ```ts -const { data: fulfillments } = await query.graph({ - entity: "fulfillment", +const { data: carts } = await query.graph({ + entity: "cart", fields: [ "order.*", ], }) -// fulfillments.order +// carts.order ``` ### useQueryGraphStep @@ -19111,14 +18815,14 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: fulfillments } = useQueryGraphStep({ - entity: "fulfillment", +const { data: carts } = useQueryGraphStep({ + entity: "cart", fields: [ "order.*", ], }) -// fulfillments.order +// carts.order ``` ### Manage with Link @@ -19133,12 +18837,12 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ + [Modules.CART]: { + cart_id: "cart_123", + }, [Modules.ORDER]: { order_id: "order_123", }, - [Modules.FULFILLMENT]: { - fulfillment_id: "ful_123", - }, }) ``` @@ -19151,40 +18855,40 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ + [Modules.CART]: { + cart_id: "cart_123", + }, [Modules.ORDER]: { order_id: "order_123", }, - [Modules.FULFILLMENT]: { - fulfillment_id: "ful_123", - }, }) ``` *** -## Pricing Module +## Payment Module -The Pricing Module provides features to store, manage, and retrieve the best prices in a specified context. +The [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md) handles payment processing and management. -Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set. +Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart. -![A diagram showcasing an example of how data models from the Pricing and Fulfillment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716561747/Medusa%20Resources/pricing-fulfillment_spywwa.jpg) +![A diagram showcasing an example of how data models from the Cart and Payment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711537849/Medusa%20Resources/cart-payment_ixziqm.jpg) ### Retrieve with Query -To retrieve the price set of a shipping option with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`: +To retrieve the payment collection of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_collection.*` in `fields`: ### query.graph ```ts -const { data: shippingOptions } = await query.graph({ - entity: "shipping_option", +const { data: carts } = await query.graph({ + entity: "cart", fields: [ - "price_set.*", + "payment_collection.*", ], }) -// shippingOptions.price_set +// carts.payment_collection ``` ### useQueryGraphStep @@ -19194,19 +18898,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: shippingOptions } = useQueryGraphStep({ - entity: "shipping_option", +const { data: carts } = useQueryGraphStep({ + entity: "cart", fields: [ - "price_set.*", + "payment_collection.*", ], }) -// shippingOptions.price_set +// carts.payment_collection ``` ### Manage with Link -To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -19216,11 +18920,11 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ - [Modules.FULFILLMENT]: { - shipping_option_id: "so_123", + [Modules.CART]: { + cart_id: "cart_123", }, - [Modules.PRICING]: { - price_set_id: "pset_123", + [Modules.PAYMENT]: { + payment_collection_id: "paycol_123", }, }) ``` @@ -19228,17 +18932,16 @@ await link.create({ ### createRemoteLinkStep ```ts -import { Modules } from "@medusajs/framework/utils" import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.FULFILLMENT]: { - shipping_option_id: "so_123", + [Modules.CART]: { + cart_id: "cart_123", }, - [Modules.PRICING]: { - price_set_id: "pset_123", + [Modules.PAYMENT]: { + payment_collection_id: "paycol_123", }, }) ``` @@ -19247,25 +18950,28 @@ createRemoteLinkStep({ ## Product Module -Medusa defines a link between the `ShippingProfile` data model and the `Product` data model of the Product Module. Each product must belong to a shipping profile. +Medusa defines read-only links between: -This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0). +- the `LineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `Product` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `LineItem` data model. +- the `LineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `ProductVariant` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `LineItem` data model. ### Retrieve with Query -To retrieve the products of a shipping profile with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`: +To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`: + +To retrieve the product, pass `product.*` in `fields`. ### query.graph ```ts -const { data: shippingProfiles } = await query.graph({ - entity: "shipping_profile", +const { data: lineItems } = await query.graph({ + entity: "line_item", fields: [ - "products.*", + "variant.*", ], }) -// shippingProfiles.products +// lineItems.variant ``` ### useQueryGraphStep @@ -19275,86 +18981,45 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: shippingProfiles } = useQueryGraphStep({ - entity: "shipping_profile", +const { data: lineItems } = useQueryGraphStep({ + entity: "line_item", fields: [ - "products.*", + "variant.*", ], }) -// shippingProfiles.products -``` - -### Manage with Link - -To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): - -### link.create - -```ts -import { Modules } from "@medusajs/framework/utils" - -// ... - -await link.create({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, - [Modules.FULFILLMENT]: { - shipping_profile_id: "sp_123", - }, -}) -``` - -### createRemoteLinkStep - -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" - -// ... - -createRemoteLinkStep({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, - [Modules.FULFILLMENT]: { - shipping_profile_id: "sp_123", - }, -}) +// lineItems.variant ``` *** -## Stock Location Module - -The Stock Location Module provides features to manage stock locations in a store. +## Promotion Module -Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models. A fulfillment set can be conditioned to a specific stock location. +The [Promotion Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/index.html.md) provides discount features. -![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1712567101/Medusa%20Resources/fulfillment-stock-location_nlkf7e.jpg) +Medusa defines a link between the `Cart` and `Promotion` data models. This indicates the promotions applied on a cart. -Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location. +![A diagram showcasing an example of how data models from the Cart and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711538015/Medusa%20Resources/cart-promotion_kuh9vm.jpg) -![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399492/Medusa%20Resources/fulfillment-provider-stock-location_b0mulo.jpg) +Medusa also defines a read-only link between the `LineItemAdjustment` and `Promotion` data models. This means you can retrieve the details of the promotion applied on a line item, but you don't manage the links in a pivot table in the database. The promotion of a line item is determined by the `promotion_id` property of the `LineItemAdjustment` data model. ### Retrieve with Query -To retrieve the stock location of a fulfillment set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `location.*` in `fields`: +To retrieve the promotions of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `promotions.*` in `fields`: -To retrieve the stock location of a fulfillment provider, pass `locations.*` in `fields`. +To retrieve the promotion of a line item adjustment, pass `promotion.*` in `fields`. ### query.graph ```ts -const { data: fulfillmentSets } = await query.graph({ - entity: "fulfillment_set", +const { data: carts } = await query.graph({ + entity: "cart", fields: [ - "location.*", + "promotions.*", ], }) -// fulfillmentSets.location +// carts.promotions ``` ### useQueryGraphStep @@ -19364,19 +19029,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: fulfillmentSets } = useQueryGraphStep({ - entity: "fulfillment_set", +const { data: carts } = useQueryGraphStep({ + entity: "cart", fields: [ - "location.*", + "promotions.*", ], }) -// fulfillmentSets.location +// carts.promotions ``` ### Manage with Link -To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -19386,11 +19051,11 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ - [Modules.STOCK_LOCATION]: { - stock_location_id: "sloc_123", + [Modules.CART]: { + cart_id: "cart_123", }, - [Modules.FULFILLMENT]: { - fulfillment_set_id: "fset_123", + [Modules.PROMOTION]: { + promotion_id: "promo_123", }, }) ``` @@ -19404,1390 +19069,1479 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.STOCK_LOCATION]: { - stock_location_id: "sloc_123", + [Modules.CART]: { + cart_id: "cart_123", }, - [Modules.FULFILLMENT]: { - fulfillment_set_id: "fset_123", + [Modules.PROMOTION]: { + promotion_id: "promo_123", }, }) ``` +*** -# Shipping Option +## Region Module -In this document, you’ll learn about shipping options and their rules. +Medusa defines a read-only link between the `Cart` data model and the [Region Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/region/index.html.md)'s `Region` data model. This means you can retrieve the details of a cart's region, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model. -## What’s a Shipping Option? +### Retrieve with Query -A shipping option is a way of shipping an item. Each fulfillment provider provides a set of shipping options. For example, a provider may provide a shipping option for express shipping and another for standard shipping. +To retrieve the region of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`: -When the customer places their order, they choose a shipping option to be used to fulfill their items. +### query.graph -A shipping option is represented by the [ShippingOption data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOption/index.html.md). +```ts +const { data: carts } = await query.graph({ + entity: "cart", + fields: [ + "region.*", + ], +}) + +// carts.region +``` + +### useQueryGraphStep + +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +// ... + +const { data: carts } = useQueryGraphStep({ + entity: "cart", + fields: [ + "region.*", + ], +}) + +// carts.region +``` *** -## Service Zone Restrictions +## Sales Channel Module -A shipping option is restricted by a service zone, limiting the locations a shipping option be used in. +Medusa defines a read-only link between the `Cart` data model and the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md)'s `SalesChannel` data model. This means you can retrieve the details of a cart's sales channel, but you don't manage the links in a pivot table in the database. The sales channel of a cart is determined by the `sales_channel_id` property of the `Cart` data model. -For example, a fulfillment provider may have a shipping option that can be used in the United States, and another in Canada. +### Retrieve with Query -![A diagram showcasing the relation between shipping options and service zones.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712330831/Medusa%20Resources/shipping-option-service-zone_pobh6k.jpg) +To retrieve the sales channel of a cart with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`: -Service zones can be more restrictive, such as restricting to certain cities or province codes. +### query.graph -![A diagram showcasing the relation between shipping options, service zones, and geo zones](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331186/Medusa%20Resources/shipping-option-service-zone-city_m5sxod.jpg) +```ts +const { data: carts } = await query.graph({ + entity: "cart", + fields: [ + "sales_channel.*", + ], +}) -*** +// carts.sales_channel +``` -## Shipping Option Rules +### useQueryGraphStep -You can restrict shipping options by custom rules, such as the item’s weight or the customer’s group. +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -These rules are represented by the [ShippingOptionRule data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionRule/index.html.md). Its properties define the custom rule: +// ... -- `attribute`: The name of a property or table that the rule applies to. For example, `customer_group`. -- `operator`: The operator used in the condition. For example: - - To allow multiple values, use the operator `in`, which validates that the provided values are in the rule’s values. - - To create a negation condition that considers `value` against the rule, use `nin`, which validates that the provided values aren’t in the rule’s values. - - Check out more operators in [this reference](https://docs.medusajs.com/references/fulfillment/types/fulfillment.RuleOperatorType/index.html.md). -- `value`: One or more values. +const { data: carts } = useQueryGraphStep({ + entity: "cart", + fields: [ + "sales_channel.*", + ], +}) -![A diagram showcasing the relation between shipping option and shipping option rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331340/Medusa%20Resources/shipping-option-rule_oosopf.jpg) +// carts.sales_channel +``` -A shipping option can have multiple rules. For example, you can add rules to a shipping option so that it's available if the customer belongs to the VIP group and the total weight is less than 2000g. -![A diagram showcasing how a shipping option can have multiple rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331462/Medusa%20Resources/shipping-option-rule-2_ylaqdb.jpg) +# Tax Lines in Cart Module -*** +In this document, you’ll learn about tax lines in a cart and how to retrieve tax lines with the Tax Module. -## Shipping Profile and Types +## What are Tax Lines? -A shipping option belongs to a type. For example, a shipping option’s type may be `express`, while another `standard`. The type is represented by the [ShippingOptionType data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionType/index.html.md). +A tax line indicates the tax rate of a line item or a shipping method. The [LineItemTaxLine data model](https://docs.medusajs.com/references/cart/models/LineItemTaxLine/index.html.md) represents a line item’s tax line, and the [ShippingMethodTaxLine data model](https://docs.medusajs.com/references/cart/models/ShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line. -A shipping option also belongs to a shipping profile, as each shipping profile defines the type of items to be shipped in a similar manner. +![A diagram showcasing the relation between other data models and the tax line models](https://res.cloudinary.com/dza7lstvk/image/upload/v1711534431/Medusa%20Resources/cart-tax-lines_oheaq6.jpg) *** -## data Property +## Tax Inclusivity -When fulfilling an item, you might use a third-party fulfillment provider that requires additional custom data to be passed along from the checkout or order-creation process. +By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount, and then adding them to the item/method’s subtotal. -The `ShippingOption` data model has a `data` property. It's an object that stores custom data relevant later when creating and processing a fulfillment. +However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes. +So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal. -# Authentication Flows with the Auth Main Service +The following diagram is a simplified showcase of how a subtotal is calculated from the taxes perspective. -In this document, you'll learn how to use the Auth Module's main service's methods to implement authentication flows and reset a user's password. +![A diagram showing an example of calculating the subtotal of a line item using its taxes](https://res.cloudinary.com/dza7lstvk/image/upload/v1711535295/Medusa%20Resources/cart-tax-inclusive_shpr3t.jpg) -## Authentication Methods +For example, if a line item's amount is `5000`, the tax rate is `10`, and tax inclusivity is enabled, the tax amount is 10% of `5000`, which is `500`, making the unit price of the line item `4500`. -### Register +*** -The [register method of the Auth Module's main service](https://docs.medusajs.com/references/auth/register/index.html.md) creates an auth identity that can be authenticated later. +## Retrieve Tax Lines -For example: +When using the Cart and Tax modules together, you can use the `getTaxLines` method of the Tax Module’s main service. It retrieves the tax lines for a cart’s line items and shipping methods. ```ts -const data = await authModuleService.register( - "emailpass", - // passed to auth provider +// retrieve the cart +const cart = await cartModuleService.retrieveCart("cart_123", { + relations: [ + "items.tax_lines", + "shipping_methods.tax_lines", + "shipping_address", + ], +}) + +// retrieve the tax lines +const taxLines = await taxModuleService.getTaxLines( + [ + ...(cart.items as TaxableItemDTO[]), + ...(cart.shipping_methods as TaxableShippingDTO[]), + ], { - // ... + address: { + ...cart.shipping_address, + country_code: + cart.shipping_address.country_code || "us", + }, } ) ``` -This method calls the `register` method of the provider specified in the first parameter and returns its data. - -### Authenticate - -To authenticate a user, you use the [authenticate method of the Auth Module's main service](https://docs.medusajs.com/references/auth/authenticate/index.html.md). For example: +Then, use the returned tax lines to set the line items and shipping methods’ tax lines: ```ts -const data = await authModuleService.authenticate( - "emailpass", - // passed to auth provider - { - // ... - } +// set line item tax lines +await cartModuleService.setLineItemTaxLines( + cart.id, + taxLines.filter((line) => "line_item_id" in line) ) -``` - -This method calls the `authenticate` method of the provider specified in the first parameter and returns its data. -*** - -## Auth Flow 1: Basic Authentication +// set shipping method tax lines +await cartModuleService.setLineItemTaxLines( + cart.id, + taxLines.filter((line) => "shipping_line_id" in line) +) +``` -The basic authentication flow requires first using the `register` method, then the `authenticate` method: -```ts -const { success, authIdentity, error } = await authModuleService.register( - "emailpass", - // passed to auth provider - { - // ... - } -) +# Customer Accounts -if (error) { - // registration failed - // TODO return an error - return -} +In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application. -// later (can be another route for log-in) -const { success, authIdentity, location } = await authModuleService.authenticate( - "emailpass", - // passed to auth provider - { - // ... - } -) +## `has_account` Property -if (success && !location) { - // user is authenticated -} -``` +The [Customer data model](https://docs.medusajs.com/references/customer/models/Customer/index.html.md) has a `has_account` property, which is a boolean that indicates whether a customer is registered. -If `success` is true and `location` isn't set, the user is authenticated successfully, and their authentication details are available within the `authIdentity` object. +When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`. -The next section explains the flow if `location` is set. +When this or another guest customer registers an account with the same email, a new `Customer` record is created with `has_account` set to `true`. -Check out the [AuthIdentity](https://docs.medusajs.com/references/auth/models/AuthIdentity/index.html.md) reference for the received properties in `authIdentity`. +*** -![Diagram showcasing the basic authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711373749/Medusa%20Resources/basic-auth_lgpqsj.jpg) +## Email Uniqueness -### Auth Identity with Same Identifier +The above behavior means that two `Customer` records may exist with the same email. However, the main difference is the `has_account` property's value. -If an auth identity, such as a `customer`, tries to register with an email of another auth identity, the `register` method returns an error. This can happen either if another customer is using the same email, or an admin user has the same email. +So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email. -There are two ways to handle this: -- Consider the customer authenticated if the `authenticate` method validates that the email and password are correct. This allows admin users, for example, to authenticate as customers. -- Return an error message to the customer, informing them that the email is already in use. +# Links between Customer Module and Other Modules -*** +This document showcases the module links defined between the Customer Module and other commerce modules. -## Auth Flow 2: Third-Party Service Authentication +## Summary -The third-party service authentication method requires using the `authenticate` method first: +The Customer Module has the following links to other modules: -```ts -const { success, authIdentity, location } = await authModuleService.authenticate( - "google", - // passed to auth provider - { - // ... - } -) +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. -if (location) { - // return the location for the front-end to redirect to -} +- [`Customer` data model \<> `AccountHolder` data model of Payment Module](#payment-module). +- [`Cart` data model of Cart Module \<> `Customer` data model](#cart-module). (Read-only). +- [`Order` data model of Order Module \<> `Customer` data model](#order-module). (Read-only). -if (!success) { - // authentication failed -} +*** -// authentication successful -``` +## Payment Module -If the `authenticate` method returns a `location` property, the authentication process requires the user to perform an action with a third-party service. So, you return the `location` to the front-end or client to redirect to that URL. +Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it. -For example, when using the `google` provider, the `location` is the URL that the user is navigated to login. +This link is available starting from Medusa `v2.5.0`. -![Diagram showcasing the first part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711374847/Medusa%20Resources/third-party-auth-1_enyedy.jpg) +### Retrieve with Query -### Overriding Callback URL +To retrieve the account holder associated with a customer with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`: -The Google and GitHub providers allow you to override their `callbackUrl` option during authentication. This is useful when you redirect the user after authentication to a URL based on its actor type. For example, you redirect admin users and customers to different pages. +### query.graph ```ts -const { success, authIdentity, location } = await authModuleService.authenticate( - "google", - // passed to auth provider - { - // ... - callback_url: "example.com", - } -) -``` +const { data: customers } = await query.graph({ + entity: "customer", + fields: [ + "account_holder.*", + ], +}) -### validateCallback +// customers.account_holder +``` -Providers handling this authentication flow must implement the `validateCallback` method. It implements the logic to validate the authentication with the third-party service. +### useQueryGraphStep -So, once the user performs the required action with the third-party service (for example, log-in with Google), the frontend must redirect to an API route that uses the [validateCallback method of the Auth Module's main service](https://docs.medusajs.com/references/auth/validateCallback/index.html.md). +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -The method calls the specified provider’s `validateCallback` method passing it the authentication details it received in the second parameter: +// ... -```ts -const { success, authIdentity } = await authModuleService.validateCallback( - "google", - // passed to auth provider - { - // request data, such as - url, - headers, - query, - body, - protocol, - } -) +const { data: customers } = useQueryGraphStep({ + entity: "customer", + fields: [ + "account_holder.*", + ], +}) -if (success) { - // authentication succeeded -} +// customers.account_holder ``` -For providers like Google, the `query` object contains the query parameters from the original callback URL, such as the `code` and `state` parameters. +### Manage with Link -If the returned `success` property is `true`, the authentication with the third-party provider was successful. +To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -![Diagram showcasing the second part of the third-party authentication flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1711375123/Medusa%20Resources/third-party-auth-2_kmjxju.jpg) +### link.create -*** +```ts +import { Modules } from "@medusajs/framework/utils" -## Reset Password +// ... -To update a user's password or other authentication details, use the `updateProvider` method of the Auth Module's main service. It calls the `update` method of the specified authentication provider. +await link.create({ + [Modules.CUSTOMER]: { + customer_id: "cus_123", + }, + [Modules.PAYMENT]: { + account_holder_id: "acchld_123", + }, +}) +``` -For example: +### createRemoteLinkStep ```ts -const { success } = await authModuleService.updateProvider( - "emailpass", - // passed to the auth provider - { - entity_id: "user@example.com", - password: "supersecret", - } -) +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -if (success) { - // password reset successfully -} +// ... + +createRemoteLinkStep({ + [Modules.CUSTOMER]: { + customer_id: "cus_123", + }, + [Modules.PAYMENT]: { + account_holder_id: "acchld_123", + }, +}) ``` -The method accepts as a first parameter the ID of the provider, and as a second parameter the data necessary to reset the password. +*** -In the example above, you use the `emailpass` provider, so you have to pass an object having an `email` and `password` properties. +## Cart Module -If the returned `success` property is `true`, the password has reset successfully. +Medusa defines a read-only link between the `Customer` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a customer's carts, but you don't manage the links in a pivot table in the database. The customer of a cart is determined by the `customer_id` property of the `Cart` data model. +### Retrieve with Query -# How to Use Authentication Routes +To retrieve a customer's carts with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: -In this document, you'll learn about the authentication routes and how to use them to create and log-in users, and reset their password. +### query.graph -These routes are added by Medusa's HTTP layer, not the Auth Module. +```ts +const { data: customers } = await query.graph({ + entity: "customer", + fields: [ + "carts.*", + ], +}) -## Types of Authentication Flows +// customers.carts +``` -### 1. Basic Authentication Flow +### useQueryGraphStep -This authentication flow doesn't require validation with third-party services. +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -[How to register customer in storefront using basic authentication flow](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md). +// ... -The steps are: +const { data: customers } = useQueryGraphStep({ + entity: "customer", + fields: [ + "carts.*", + ], +}) -![Diagram showcasing the basic authentication flow between the frontend and the Medusa application](https://res.cloudinary.com/dza7lstvk/image/upload/v1725539370/Medusa%20Resources/basic-auth-routes_pgpjch.jpg) +// customers.carts +``` -1. Register the user with the [Register Route](#register-route). -2. Use the authentication token to create the user with their respective API route. - - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers). - - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept) -3. Authenticate the user with the [Auth Route](#login-route). +*** -After registration, you only use the [Auth Route](#login-route) for subsequent authentication. +## Order Module -To handle errors related to existing identities, refer to [this section](#handling-existing-identities). +Medusa defines a read-only link between the `Customer` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model. This means you can retrieve the details of a customer's orders, but you don't manage the links in a pivot table in the database. The customer of an order is determined by the `customer_id` property of the `Order` data model. -### 2. Third-Party Service Authenticate Flow +### Retrieve with Query -This authentication flow authenticates the user with a third-party service, such as Google. +To retrieve a customer's orders with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: -[How to authenticate customer with a third-party provider in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md). +### query.graph -It requires the following steps: +```ts +const { data: customers } = await query.graph({ + entity: "customer", + fields: [ + "orders.*", + ], +}) -![Diagram showcasing the authentication flow between the frontend, Medusa application, and third-party service](https://res.cloudinary.com/dza7lstvk/image/upload/v1725528159/Medusa%20Resources/Third_Party_Auth_tvf4ng.jpg) +// customers.orders +``` -1. Authenticate the user with the [Auth Route](#login-route). -2. The auth route returns a URL to authenticate with third-party service, such as login with Google. The frontend (such as a storefront), when it receives a `location` property in the response, must redirect to the returned location. -3. Once the authentication with the third-party service finishes, it redirects back to the frontend with a `code` query parameter. So, make sure your third-party service is configured to redirect to your frontend page after successful authentication. -4. The frontend sends a request to the [Validate Callback Route](#validate-callback-route) passing it the query parameters received from the third-party service, such as the `code` and `state` query parameters. -5. If the callback validation is successful, the frontend receives the authentication token. -6. Decode the received token in the frontend using tools like [react-jwt](https://www.npmjs.com/package/react-jwt). - - If the decoded data has an `actor_id` property, then the user is already registered. So, use this token for subsequent authenticated requests. - - If not, follow the rest of the steps. -7. The frontend uses the authentication token to create the user with their respective API route. - - For example, for customers you would use the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers). - - For admin users, you accept an invite using the [Accept Invite API route](https://docs.medusajs.com/api/admin#invites_postinvitesaccept) -8. The frontend sends a request to the [Refresh Token Route](#refresh-token-route) to retrieve a new token with the user information populated. +### useQueryGraphStep -*** +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -## Register Route +// ... -The Medusa application defines an API route at `/auth/{actor_type}/{provider}/register` that creates an auth identity for an actor type, such as a `customer`. It returns a JWT token that you pass to an API route that creates the user. +const { data: customers } = useQueryGraphStep({ + entity: "customer", + fields: [ + "orders.*", + ], +}) -```bash -curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/register --H 'Content-Type: application/json' \ ---data-raw '{ - "email": "Whitney_Schultz@gmail.com" - // ... -}' +// customers.orders ``` -This API route is useful for providers like `emailpass` that uses custom logic to authenticate a user. For authentication providers that authenticate with third-party services, such as Google, use the [Auth Route](#login-route) instead. -For example, if you're registering a customer, you: +# Fulfillment Concepts -1. Send a request to `/auth/customer/emailpass/register` to retrieve the registration JWT token. -2. Send a request to the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers) to create the customer, passing the [JWT token in the header](https://docs.medusajs.com/api/store#authentication). +In this document, you’ll learn about some basic fulfillment concepts. -### Path Parameters +## Fulfillment Set -Its path parameters are: +A fulfillment set is a general form or way of fulfillment. For example, shipping is a form of fulfillment, and pick-up is another form of fulfillment. Each of these can be created as fulfillment sets. -- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. -- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`. +A fulfillment set is represented by the [FulfillmentSet data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentSet/index.html.md). All other configurations, options, and management features are related to a fulfillment set, in one way or another. -### Request Body Parameters +```ts +const fulfillmentSets = await fulfillmentModuleService.createFulfillmentSets( + [ + { + name: "Shipping", + type: "shipping", + }, + { + name: "Pick-up", + type: "pick-up", + }, + ] +) +``` -This route accepts in the request body the data that the specified authentication provider requires to handle authentication. +*** -For example, the EmailPass provider requires an `email` and `password` fields in the request body. +## Service Zone -### Response Fields +A service zone is a collection of geographical zones or areas. It’s used to restrict available shipping options to a defined set of locations. -If the authentication is successful, you'll receive a `token` field in the response body object: +A service zone is represented by the [ServiceZone data model](https://docs.medusajs.com/references/fulfillment/models/ServiceZone/index.html.md). It’s associated with a fulfillment set, as each service zone is specific to a form of fulfillment. For example, if a customer chooses to pick up items, you can restrict the available shipping options based on their location. -```json -{ - "token": "..." -} -``` +![A diagram showcasing the relation between fulfillment sets, service zones, and geo zones](https://res.cloudinary.com/dza7lstvk/image/upload/v1712329770/Medusa%20Resources/service-zone_awmvfs.jpg) -Use that token in the header of subsequent requests to send authenticated requests. +A service zone can have multiple geographical zones, each represented by the [GeoZone data model](https://docs.medusajs.com/references/fulfillment/models/GeoZone/index.html.md). It holds location-related details to narrow down supported areas, such as country, city, or province code. -### Handling Existing Identities +*** -An auth identity with the same email may already exist in Medusa. This can happen if: +## Shipping Profile -- Another actor type is using that email. For example, an admin user is trying to register as a customer. -- The same email belongs to a record of the same actor type. For example, another customer has the same email. +A shipping profile defines a type of items that are shipped in a similar manner. For example, a `default` shipping profile is used for all item types, but the `digital` shipping profile is used for digital items that aren’t shipped and delivered conventionally. -In these scenarios, the Register Route will return an error instead of a token: +A shipping profile is represented by the [ShippingProfile data model](https://docs.medusajs.com/references/fulfillment/models/ShippingProfile/index.html.md). It only defines the profile’s details, but it’s associated with the shipping options available for the item type. -```json -{ - "type": "unauthorized", - "message": "Identity with email already exists" -} -``` -To handle these scenarios, you can use the [Login Route](#login-route) to validate that the email and password match the existing identity. If so, you can allow the admin user, for example, to register as a customer. +# Fulfillment Module Provider -Otherwise, if the email and password don't match the existing identity, such as when the email belongs to another customer, the [Login Route](#login-route) returns an error: +In this document, you’ll learn what a fulfillment module provider is. -```json -{ - "type": "unauthorized", - "message": "Invalid email or password" -} -``` +## What’s a Fulfillment Module Provider? -You can show that error message to the customer. +A fulfillment module provider handles fulfilling items, typically using a third-party integration. -*** +Fulfillment module providers registered in the Fulfillment Module's [options](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md) are stored and represented by the [FulfillmentProvider data model](https://docs.medusajs.com/references/fulfillment/models/FulfillmentProvider/index.html.md). -## Login Route +*** -The Medusa application defines an API route at `/auth/{actor_type}/{provider}` that authenticates a user of an actor type. It returns a JWT token that can be passed in [the header of subsequent requests](https://docs.medusajs.com/api/store#authentication) to send authenticated requests. +## Configure Fulfillment Providers -```bash -curl -X POST http://localhost:9000/auth/{actor_type}/{providers} --H 'Content-Type: application/json' \ ---data-raw '{ - "email": "Whitney_Schultz@gmail.com" - // ... -}' -``` +The Fulfillment Module accepts a `providers` option that allows you to register providers in your application. -For example, if you're authenticating a customer, you send a request to `/auth/customer/emailpass`. +Learn more about the `providers` option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/fulfillment/module-options/index.html.md). -### Path Parameters +*** -Its path parameters are: +## How to Create a Fulfillment Provider? -- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. -- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`. +Refer to [this guide](https://docs.medusajs.com/references/fulfillment/provider/index.html.md) to learn how to create a fulfillment module provider. -### Request Body Parameters -This route accepts in the request body the data that the specified authentication provider requires to handle authentication. +# Item Fulfillment -For example, the EmailPass provider requires an `email` and `password` fields in the request body. +In this document, you’ll learn about the concepts of item fulfillment. -#### Overriding Callback URL +## Fulfillment Data Model -For the [GitHub](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/github/index.html.md) and [Google](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/auth-providers/google/index.html.md) providers, you can pass a `callback_url` body parameter that overrides the `callbackUrl` set in the provider's configurations. +A fulfillment is the shipping and delivery of one or more items to the customer. It’s represented by the [Fulfillment data model](https://docs.medusajs.com/references/fulfillment/models/Fulfillment/index.html.md). -This is useful if you want to redirect the user to a different URL after authentication based on their actor type. For example, you can set different `callback_url` for admin users and customers. +*** -### Response Fields +## Fulfillment Processing by a Fulfillment Provider -If the authentication is successful, you'll receive a `token` field in the response body object: +A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items. -```json -{ - "token": "..." -} -``` +The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped. -Use that token in the header of subsequent requests to send authenticated requests. +![A diagram showcasing the relation between a fulfillment, fulfillment provider, and shipping option](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331947/Medusa%20Resources/fulfillment-shipping-option_jk9ndp.jpg) -If the authentication requires more action with a third-party service, you'll receive a `location` property: +*** -```json -{ - "location": "https://..." -} -``` +## data Property -Redirect to that URL in the frontend to continue the authentication process with the third-party service. +The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment. -[How to login Customers using the authentication route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/login/index.html.md). +For example, the `data` property can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details. *** -## Validate Callback Route +## Fulfillment Items -The Medusa application defines an API route at `/auth/{actor_type}/{provider}/callback` that's useful for validating the authentication callback or redirect from third-party services like Google. +A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model. -```bash -curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/callback?code=123&state=456 -``` +The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill. -Refer to the [third-party authentication flow](#2-third-party-service-authenticate-flow) section to see how this route fits into the authentication flow. +![A diagram showcasing the relation between fulfillment and fulfillment items.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712332114/Medusa%20Resources/fulfillment-item_etzxb0.jpg) -### Path Parameters +*** -Its path parameters are: +## Fulfillment Label -- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. -- `{provider}`: the auth provider to handle the authentication. For example, `google`. +Once a shipment is created for the fulfillment, you can store its tracking number, URL, or other related details as a label, represented by the `FulfillmentLabel` data model. -### Query Parameters +*** -This route accepts all the query parameters that the third-party service sends to the frontend after the user completes the authentication process, such as the `code` and `state` query parameters. +## Fulfillment Status -### Response Fields +The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment: -If the authentication is successful, you'll receive a `token` field in the response body object: +- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed. +- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped. +- `delivered_at`: The date the fulfillment was delivered. If set, then the fulfillment has been delivered. -```json -{ - "token": "..." -} -``` -In your frontend, decode the token using tools like [react-jwt](https://www.npmjs.com/package/react-jwt): +# Links between Fulfillment Module and Other Modules -- If the decoded data has an `actor_id` property, the user is already registered. So, use this token for subsequent authenticated requests. -- If not, use the token in the header of a request that creates the user, such as the [Create Customer API route](https://docs.medusajs.com/api/store#customers_postcustomers). +This document showcases the module links defined between the Fulfillment Module and other commerce modules. -*** +## Summary -## Refresh Token Route +The Fulfillment Module has the following links to other modules: -The Medusa application defines an API route at `/auth/token/refresh` that's useful after authenticating a user with a third-party service to populate the user's token with their new information. +- [`Order` data model of the Order Module \<> `Fulfillment` data model](#order-module). +- [`Return` data model of the Order Module \<> `Fulfillment` data model](#order-module). +- [`PriceSet` data model of the Pricing Module \<> `ShippingOption` data model](#pricing-module). +- [`Product` data model of the Product Module \<> `ShippingProfile` data model](#product-module). +- [`StockLocation` data model of the Stock Location Module \<> `FulfillmentProvider` data model](#stock-location-module). +- [`StockLocation` data model of the Stock Location Module \<> `FulfillmentSet` data model](#stock-location-module). -It requires the user's JWT token that they received from the authentication or callback routes. +*** -```bash -curl -X POST http://localhost:9000/auth/token/refresh \ --H 'Authorization: Bearer {token}' -``` +## Order Module -### Response Fields +The [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md) provides order-management functionalities. -If the token was refreshed successfully, you'll receive a `token` field in the response body object: +Medusa defines a link between the `Fulfillment` and `Order` data models. A fulfillment is created for an orders' items. -```json -{ - "token": "..." -} -``` +![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716549903/Medusa%20Resources/order-fulfillment_h0vlps.jpg) -Use that token in the header of subsequent requests to send authenticated requests. +A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models. -*** +![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399052/Medusa%20Resources/Social_Media_Graphics_2024_Order_Return_vetimk.jpg) -## Reset Password Routes +### Retrieve with Query -To reset a user's password: +To retrieve the order of a fulfillment with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`: -1. Generate a token using the [Generate Reset Password Token API route](#generate-reset-password-token-route). - - The API route emits the `auth.password_reset` event, passing the token in the payload. - - You can create a subscriber, as seen in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/reset-password/index.html.md), that listens to the event and send a notification to the user. -2. Pass the token to the [Reset Password API route](#reset-password-route) to reset the password. - - The URL in the user's notification should direct them to a frontend URL, which sends a request to this route. +To retrieve the return, pass `return.*` in `fields`. -[Storefront Development: How to Reset a Customer's Password.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/reset-password/index.html.md) +### query.graph -### Generate Reset Password Token Route +```ts +const { data: fulfillments } = await query.graph({ + entity: "fulfillment", + fields: [ + "order.*", + ], +}) -The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/reset-password` that emits the `auth.password_reset` event, passing the token in the payload. +// fulfillments.order +``` -```bash -curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/reset-password --H 'Content-Type: application/json' \ ---data-raw '{ - "identifier": "Whitney_Schultz@gmail.com" -}' -``` - -This API route is useful for providers like `emailpass` that store a user's password and use it for authentication. - -#### Path Parameters +### useQueryGraphStep -Its path parameters are: +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. -- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`. +// ... -#### Request Body Parameters +const { data: fulfillments } = useQueryGraphStep({ + entity: "fulfillment", + fields: [ + "order.*", + ], +}) -This route accepts in the request body an object having the following property: +// fulfillments.order +``` -- `identifier`: The user's identifier in the specified auth provider. For example, for the `emailpass` auth provider, you pass the user's email. +### Manage with Link -#### Response Fields +To manage the order of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -If the authentication is successful, the request returns a `201` response code. +### link.create -### Reset Password Route +```ts +import { Modules } from "@medusajs/framework/utils" -The Medusa application defines an API route at `/auth/{actor_type}/{auth_provider}/update` that accepts a token and, if valid, updates the user's password. +// ... -```bash -curl -X POST http://localhost:9000/auth/{actor_type}/{providers}/update?token=123 --H 'Content-Type: application/json' \ ---data-raw '{ - "email": "Whitney_Schultz@gmail.com", - "password": "supersecret" -}' +await link.create({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_id: "ful_123", + }, +}) ``` -This API route is useful for providers like `emailpass` that store a user's password and use it for logging them in. +### createRemoteLinkStep -#### Path Parameters +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -Its path parameters are: +// ... -- `{actor_type}`: the actor type of the user you're authenticating. For example, `customer`. -- `{provider}`: the auth provider to handle the authentication. For example, `emailpass`. +createRemoteLinkStep({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_id: "ful_123", + }, +}) +``` -#### Query Parameters +*** -The route accepts a `token` query parameter, which is the token generated using the [Generate Reset Password Token route](#generate-reset-password-token-route). +## Pricing Module -### Request Body Parameters +The Pricing Module provides features to store, manage, and retrieve the best prices in a specified context. -This route accepts in the request body an object that has the data necessary for the provider to update the user's password. +Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set. -For the `emailpass` provider, you must pass the following properties: +![A diagram showcasing an example of how data models from the Pricing and Fulfillment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716561747/Medusa%20Resources/pricing-fulfillment_spywwa.jpg) -- `email`: The user's email. -- `password`: The new password. +### Retrieve with Query -### Response Fields +To retrieve the price set of a shipping option with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`: -If the authentication is successful, the request returns an object with a `success` property set to `true`: +### query.graph -```json -{ - "success": "true" -} +```ts +const { data: shippingOptions } = await query.graph({ + entity: "shipping_option", + fields: [ + "price_set.*", + ], +}) + +// shippingOptions.price_set ``` +### useQueryGraphStep -# Inventory Concepts +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -In this document, you’ll learn about the main concepts in the Inventory Module, and how data is stored and related. +// ... -## InventoryItem +const { data: shippingOptions } = useQueryGraphStep({ + entity: "shipping_option", + fields: [ + "price_set.*", + ], +}) -An inventory item, represented by the [InventoryItem data model](https://docs.medusajs.com/references/inventory-next/models/InventoryItem/index.html.md), is a stock-kept item, such as a product, whose inventory can be managed. +// shippingOptions.price_set +``` -The `InventoryItem` data model mainly holds details related to the underlying stock item, but has relations to other data models that include its inventory details. +### Manage with Link -![A diagram showcasing the relation between data models in the Inventory Module](https://res.cloudinary.com/dza7lstvk/image/upload/v1709658103/Medusa%20Resources/inventory-architecture_kxr2ql.png) +To manage the price set of a shipping option, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -*** +### link.create -## InventoryLevel +```ts +import { Modules } from "@medusajs/framework/utils" -An inventory level, represented by the [InventoryLevel data model](https://docs.medusajs.com/references/inventory-next/models/InventoryLevel/index.html.md), holds the inventory and quantity details of an inventory item in a specific location. +// ... -It has three quantity-related properties: +await link.create({ + [Modules.FULFILLMENT]: { + shipping_option_id: "so_123", + }, + [Modules.PRICING]: { + price_set_id: "pset_123", + }, +}) +``` -- `stocked_quantity`: The available stock quantity of an item in the associated location. -- `reserved_quantity`: The quantity reserved from the available `stocked_quantity`. It indicates the quantity that's still not removed from stock, but considered as unavailable when checking whether an item is in stock. -- `incoming_quantity`: The incoming stock quantity of an item into the associated location. This property doesn't play into the `stocked_quantity` or when checking whether an item is in stock. +### createRemoteLinkStep -### Associated Location +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -The inventory level's location is determined by the `location_id` property. Medusa links the `InventoryLevel` data model with the `StockLocation` data model from the Stock Location Module. +// ... + +createRemoteLinkStep({ + [Modules.FULFILLMENT]: { + shipping_option_id: "so_123", + }, + [Modules.PRICING]: { + price_set_id: "pset_123", + }, +}) +``` *** -## ReservationItem +## Product Module -A reservation item, represented by the [ReservationItem](https://docs.medusajs.com/references/inventory-next/models/ReservationItem/index.html.md) data model, represents unavailable quantity of an inventory item in a location. It's used when an order is placed but not fulfilled yet. +Medusa defines a link between the `ShippingProfile` data model and the `Product` data model of the Product Module. Each product must belong to a shipping profile. -The reserved quantity is associated with a location, so it has a similar relation to that of the `InventoryLevel` with the Stock Location Module. +This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0). +### Retrieve with Query -# Inventory Module in Medusa Flows +To retrieve the products of a shipping profile with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`: -This document explains how the Inventory Module is used within the Medusa application's flows. +### query.graph -## Product Variant Creation +```ts +const { data: shippingProfiles } = await query.graph({ + entity: "shipping_profile", + fields: [ + "products.*", + ], +}) -When a product variant is created and its `manage_inventory` property's value is `true`, the Medusa application creates an inventory item associated with that product variant. +// shippingProfiles.products +``` -This flow is implemented within the [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md) +### useQueryGraphStep -![A diagram showcasing how the Inventory Module is used in the product variant creation form](https://res.cloudinary.com/dza7lstvk/image/upload/v1709661511/Medusa%20Resources/inventory-product-create_khz2hk.jpg) +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -*** +// ... -## Add to Cart +const { data: shippingProfiles } = useQueryGraphStep({ + entity: "shipping_profile", + fields: [ + "products.*", + ], +}) -When a product variant with `manage_inventory` set to `true` is added to cart, the Medusa application checks whether there's sufficient stocked quantity. If not, an error is thrown and the product variant won't be added to the cart. +// shippingProfiles.products +``` -This flow is implemented within the [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md) +### Manage with Link -![A diagram showcasing how the Inventory Module is used in the add to cart flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1709711645/Medusa%20Resources/inventory-cart-flow_achwq9.jpg) +To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -*** +### link.create -## Order Placed +```ts +import { Modules } from "@medusajs/framework/utils" -When an order is placed, the Medusa application creates a reservation item for each product variant with `manage_inventory` set to `true`. +// ... -This flow is implemented within the [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md) +await link.create({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, + [Modules.FULFILLMENT]: { + shipping_profile_id: "sp_123", + }, +}) +``` -![A diagram showcasing how the Inventory Module is used in the order placed flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1709712005/Medusa%20Resources/inventory-order-placed_qdxqdn.jpg) +### createRemoteLinkStep -*** +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -## Order Fulfillment +// ... -When an item in an order is fulfilled and the associated variant has its `manage_inventory` property set to `true`, the Medusa application: +createRemoteLinkStep({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, + [Modules.FULFILLMENT]: { + shipping_profile_id: "sp_123", + }, +}) +``` -- Subtracts the `reserved_quantity` from the `stocked_quantity` in the inventory level associated with the variant's inventory item. -- Resets the `reserved_quantity` to `0`. -- Deletes the associated reservation item. +*** -This flow is implemented within the [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md) +## Stock Location Module -![A diagram showcasing how the Inventory Module is used in the order fulfillment flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1709712390/Medusa%20Resources/inventory-order-fulfillment_o9wdxh.jpg) +The Stock Location Module provides features to manage stock locations in a store. -*** +Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models. A fulfillment set can be conditioned to a specific stock location. -## Order Return +![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1712567101/Medusa%20Resources/fulfillment-stock-location_nlkf7e.jpg) -When an item in an order is returned and the associated variant has its `manage_inventory` property set to `true`, the Medusa application increments the `stocked_quantity` of the inventory item's level with the returned quantity. +Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location. -This flow is implemented within the [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md) +![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399492/Medusa%20Resources/fulfillment-provider-stock-location_b0mulo.jpg) -![A diagram showcasing how the Inventory Module is used in the order return flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1709712457/Medusa%20Resources/inventory-order-return_ihftyk.jpg) +### Retrieve with Query -### Dismissed Returned Items +To retrieve the stock location of a fulfillment set with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `location.*` in `fields`: -If a returned item is considered damaged or is dismissed, its quantity doesn't increment the `stocked_quantity` of the inventory item's level. +To retrieve the stock location of a fulfillment provider, pass `locations.*` in `fields`. +### query.graph -# Inventory Kits - -In this guide, you'll learn how inventory kits can be used in the Medusa application to support use cases like multi-part products, bundled products, and shared inventory across products. +```ts +const { data: fulfillmentSets } = await query.graph({ + entity: "fulfillment_set", + fields: [ + "location.*", + ], +}) -## What is an Inventory Kit? +// fulfillmentSets.location +``` -An inventory kit is a collection of inventory items that are linked to a single product variant. These inventory items can be used to represent different parts of a product, or to represent a bundle of products. +### useQueryGraphStep -The Medusa application links inventory items from the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to product variants in the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md). Each variant can have multiple inventory items, and these inventory items can be re-used or shared across variants. +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -Using inventory kits, you can implement use cases like: +// ... -- [Multi-part products](#multi-part-products): A product that consists of multiple parts, each with its own inventory item. -- [Bundled products](#bundled-products): A product that is sold as a bundle, where each variant in the bundle product can re-use the inventory items of another product that should be sold as part of the bundle. +const { data: fulfillmentSets } = useQueryGraphStep({ + entity: "fulfillment_set", + fields: [ + "location.*", + ], +}) -*** +// fulfillmentSets.location +``` -## Multi-Part Products +### Manage with Link -Consider your store sells bicycles that consist of a frame, wheels, and seats, and you want to manage the inventory of these parts separately. +To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -To implement this in Medusa, you can: +### link.create -- Create inventory items for each of the different parts. -- For each bicycle product, add a variant whose inventory kit consists of the inventory items of each of the parts. +```ts +import { Modules } from "@medusajs/framework/utils" -Then, whenever a customer purchases a bicycle, the inventory of each part is updated accordingly. You can also use the `required_quantity` of the variant's inventory items to set how much quantity is consumed of the part's inventory when a bicycle is sold. For example, the bicycle's wheels require 2 wheels inventory items to be sold when a bicycle is sold. +// ... -![Diagram showcasing how a variant is linked to multi-part inventory items](https://res.cloudinary.com/dza7lstvk/image/upload/v1736414257/Medusa%20Resources/multi-part-product_kepbnx.jpg) +await link.create({ + [Modules.STOCK_LOCATION]: { + stock_location_id: "sloc_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_set_id: "fset_123", + }, +}) +``` -### Create Multi-Part Product +### createRemoteLinkStep -Using the Medusa Admin, you can create a multi-part product by creating its inventory items first, then assigning these inventory items to the product's variant(s). +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -Using [workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), you can implement this by first creating the inventory items: +// ... -```ts highlights={multiPartsHighlights1} -import { - createInventoryItemsWorkflow, - useQueryGraphStep, -} from "@medusajs/medusa/core-flows" -import { createWorkflow } from "@medusajs/framework/workflows-sdk" +createRemoteLinkStep({ + [Modules.STOCK_LOCATION]: { + stock_location_id: "sloc_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_set_id: "fset_123", + }, +}) +``` -export const createMultiPartProductsWorkflow = createWorkflow( - "create-multi-part-products", - () => { - // Alternatively, you can create a stock location - const { data: stockLocations } = useQueryGraphStep({ - entity: "stock_location", - fields: ["*"], - filters: { - name: "European Warehouse", - }, - }) - const inventoryItems = createInventoryItemsWorkflow.runAsStep({ - input: { - items: [ - { - sku: "FRAME", - title: "Frame", - location_levels: [ - { - stocked_quantity: 100, - location_id: stockLocations[0].id, - }, - ], - }, - { - sku: "WHEEL", - title: "Wheel", - location_levels: [ - { - stocked_quantity: 100, - location_id: stockLocations[0].id, - }, - ], - }, - { - sku: "SEAT", - title: "Seat", - location_levels: [ - { - stocked_quantity: 100, - location_id: stockLocations[0].id, - }, - ], - }, - ], - }, - }) +# Fulfillment Module Options - // TODO create the product - } -) -``` +In this document, you'll learn about the options of the Fulfillment Module. -You start by retrieving the stock location to create the inventory items in. Alternatively, you can [create a stock location](https://docs.medusajs.com/references/medusa-workflows/createStockLocationsWorkflow/index.html.md). +## providers -Then, you create the inventory items that the product variant consists of. +The `providers` option is an array of fulfillment module providers. -Next, create the product and pass the inventory item's IDs to the product's variant: +When the Medusa application starts, these providers are registered and can be used to process fulfillments. -```ts highlights={multiPartHighlights2} -import { - // ... - transform, -} from "@medusajs/framework/workflows-sdk" -import { - // ... - createProductsWorkflow, -} from "@medusajs/medusa/core-flows" +For example: -export const createMultiPartProductsWorkflow = createWorkflow( - "create-multi-part-products", - () => { - // ... +```ts title="medusa-config.ts" +import { Modules } from "@medusajs/framework/utils" - const inventoryItemIds = transform({ - inventoryItems, - }, (data) => { - return data.inventoryItems.map((inventoryItem) => { - return { - inventory_item_id: inventoryItem.id, - // can also specify required_quantity - } - }) - }) +// ... - const products = createProductsWorkflow.runAsStep({ - input: { - products: [ +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/fulfillment", + options: { + providers: [ { - title: "Bicycle", - variants: [ - { - title: "Bicycle - Small", - prices: [ - { - amount: 100, - currency_code: "usd", - }, - ], - options: { - "Default Option": "Default Variant", - }, - inventory_items: inventoryItemIds, - }, - ], - options: [ - { - title: "Default Option", - values: ["Default Variant"], - }, - ], - shipping_profile_id: "sp_123", + resolve: `@medusajs/medusa/fulfillment-manual`, + id: "manual", + options: { + // provider options... + }, }, ], }, - }) - } -) + }, + ], +}) ``` -You prepare the inventory item IDs to pass to the variant using [transform](https://docs.medusajs.com/docs/learn/fundamentals/workflows/variable-manipulation/index.html.md) from the Workflows SDK, then pass these IDs to the created product's variant. +The `providers` option is an array of objects that accept the following properties: -You can now [execute the workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows#3-execute-the-workflow/index.html.md) in [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md), [scheduled jobs](https://docs.medusajs.com/docs/learn/fundamentals/scheduled-jobs/index.html.md), or [subscribers](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md). +- `resolve`: A string indicating either the package name of the module provider or the path to it relative to the `src` directory. +- `id`: A string indicating the provider's unique name or ID. +- `options`: An optional object of the module provider's options. + + +# Shipping Option + +In this document, you’ll learn about shipping options and their rules. + +## What’s a Shipping Option? + +A shipping option is a way of shipping an item. Each fulfillment provider provides a set of shipping options. For example, a provider may provide a shipping option for express shipping and another for standard shipping. + +When the customer places their order, they choose a shipping option to be used to fulfill their items. + +A shipping option is represented by the [ShippingOption data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOption/index.html.md). *** -## Bundled Products +## Service Zone Restrictions -Consider you have three products: shirt, pants, and shoes. You sell those products separately, but you also want to offer them as a bundle. +A shipping option is restricted by a service zone, limiting the locations a shipping option be used in. -![Diagram showcasing products each having their own variants and inventory](https://res.cloudinary.com/dza7lstvk/image/upload/v1736414787/Medusa%20Resources/bundled-product-1_vmzewk.jpg) +For example, a fulfillment provider may have a shipping option that can be used in the United States, and another in Canada. -You can do that by creating a product, where each variant re-uses the inventory items of each of the shirt, pants, and shoes products. +![A diagram showcasing the relation between shipping options and service zones.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712330831/Medusa%20Resources/shipping-option-service-zone_pobh6k.jpg) -Then, when the bundled product's variant is purchased, the inventory quantity of the associated inventory items are updated. +Service zones can be more restrictive, such as restricting to certain cities or province codes. -![Diagram showcasing a bundled product using the same inventory as the products part of the bundle](https://res.cloudinary.com/dza7lstvk/image/upload/v1736414780/Medusa%20Resources/bundled-product_x94ca1.jpg) +![A diagram showcasing the relation between shipping options, service zones, and geo zones](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331186/Medusa%20Resources/shipping-option-service-zone-city_m5sxod.jpg) -### Create Bundled Product +*** -You can create a bundled product in the Medusa Admin by creating the products part of the bundle first, each having its own inventory items. Then, you create the bundled product whose variant(s) have inventory kits composed of inventory items from each of the products part of the bundle. +## Shipping Option Rules -Using [workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), you can implement this by first creating the products part of the bundle: +You can restrict shipping options by custom rules, such as the item’s weight or the customer’s group. -```ts highlights={bundledHighlights1} -import { - createWorkflow, -} from "@medusajs/framework/workflows-sdk" -import { - createProductsWorkflow, -} from "@medusajs/medusa/core-flows" +These rules are represented by the [ShippingOptionRule data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionRule/index.html.md). Its properties define the custom rule: -export const createBundledProducts = createWorkflow( - "create-bundled-products", - () => { - const products = createProductsWorkflow.runAsStep({ - input: { - products: [ - { - title: "Shirt", - shipping_profile_id: "sp_123", - variants: [ - { - title: "Shirt", - prices: [ - { - amount: 10, - currency_code: "usd", - }, - ], - options: { - "Default Option": "Default Variant", - }, - manage_inventory: true, - }, - ], - options: [ - { - title: "Default Option", - values: ["Default Variant"], - }, - ], - }, - { - title: "Pants", - shipping_profile_id: "sp_123", - variants: [ - { - title: "Pants", - prices: [ - { - amount: 10, - currency_code: "usd", - }, - ], - options: { - "Default Option": "Default Variant", - }, - manage_inventory: true, - }, - ], - options: [ - { - title: "Default Option", - values: ["Default Variant"], - }, - ], - }, - { - title: "Shoes", - shipping_profile_id: "sp_123", - variants: [ - { - title: "Shoes", - prices: [ - { - amount: 10, - currency_code: "usd", - }, - ], - options: { - "Default Option": "Default Variant", - }, - manage_inventory: true, - }, - ], - options: [ - { - title: "Default Option", - values: ["Default Variant"], - }, - ], - }, - ], - }, - }) - - // TODO re-retrieve with inventory - } -) -``` - -You create three products and enable `manage_inventory` for their variants, which will create a default inventory item. You can also create the inventory item first for more control over the quantity as explained in [the previous section](#create-multi-part-product). +- `attribute`: The name of a property or table that the rule applies to. For example, `customer_group`. +- `operator`: The operator used in the condition. For example: + - To allow multiple values, use the operator `in`, which validates that the provided values are in the rule’s values. + - To create a negation condition that considers `value` against the rule, use `nin`, which validates that the provided values aren’t in the rule’s values. + - Check out more operators in [this reference](https://docs.medusajs.com/references/fulfillment/types/fulfillment.RuleOperatorType/index.html.md). +- `value`: One or more values. -Next, retrieve the products again but with variant information: +![A diagram showcasing the relation between shipping option and shipping option rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331340/Medusa%20Resources/shipping-option-rule_oosopf.jpg) -```ts highlights={bundledHighlights2} -import { - // ... - transform, -} from "@medusajs/framework/workflows-sdk" -import { - useQueryGraphStep, -} from "@medusajs/medusa/core-flows" +A shipping option can have multiple rules. For example, you can add rules to a shipping option so that it's available if the customer belongs to the VIP group and the total weight is less than 2000g. -export const createBundledProducts = createWorkflow( - "create-bundled-products", - () => { - // ... - const productIds = transform({ - products, - }, (data) => data.products.map((product) => product.id)) +![A diagram showcasing how a shipping option can have multiple rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712331462/Medusa%20Resources/shipping-option-rule-2_ylaqdb.jpg) - // @ts-ignore - const { data: productsWithInventory } = useQueryGraphStep({ - entity: "product", - fields: [ - "variants.*", - "variants.inventory_items.*", - ], - filters: { - id: productIds, - }, - }) +*** - const inventoryItemIds = transform({ - productsWithInventory, - }, (data) => { - return data.productsWithInventory.map((product) => { - return { - inventory_item_id: product.variants[0].inventory_items?.[0]?.inventory_item_id, - } - }) - }) +## Shipping Profile and Types - // create bundled product - } -) -``` +A shipping option belongs to a type. For example, a shipping option’s type may be `express`, while another `standard`. The type is represented by the [ShippingOptionType data model](https://docs.medusajs.com/references/fulfillment/models/ShippingOptionType/index.html.md). -Using [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), you retrieve the product again with the inventory items of each variant. Then, you prepare the inventory items to pass to the bundled product's variant. +A shipping option also belongs to a shipping profile, as each shipping profile defines the type of items to be shipped in a similar manner. -Finally, create the bundled product: +*** -```ts highlights={bundledProductHighlights3} -export const createBundledProducts = createWorkflow( - "create-bundled-products", - () => { - // ... - const bundledProduct = createProductsWorkflow.runAsStep({ - input: { - products: [ - { - title: "Bundled Clothes", - shipping_profile_id: "sp_123", - variants: [ - { - title: "Bundle", - prices: [ - { - amount: 30, - currency_code: "usd", - }, - ], - options: { - "Default Option": "Default Variant", - }, - inventory_items: inventoryItemIds, - }, - ], - options: [ - { - title: "Default Option", - values: ["Default Variant"], - }, - ], - }, - ], - }, - }).config({ name: "create-bundled-product" }) - } -) -``` +## data Property -The bundled product has the same inventory items as those of the products part of the bundle. +When fulfilling an item, you might use a third-party fulfillment provider that requires additional custom data to be passed along from the checkout or order-creation process. -You can now [execute the workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows#3-execute-the-workflow/index.html.md) in [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md), [scheduled jobs](https://docs.medusajs.com/docs/learn/fundamentals/scheduled-jobs/index.html.md), or [subscribers](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md). +The `ShippingOption` data model has a `data` property. It's an object that stores custom data relevant later when creating and processing a fulfillment. -# Links between Inventory Module and Other Modules +# Inventory Concepts -This document showcases the module links defined between the Inventory Module and other commerce modules. +In this document, you’ll learn about the main concepts in the Inventory Module, and how data is stored and related. -## Summary +## InventoryItem -The Inventory Module has the following links to other modules: +An inventory item, represented by the [InventoryItem data model](https://docs.medusajs.com/references/inventory-next/models/InventoryItem/index.html.md), is a stock-kept item, such as a product, whose inventory can be managed. -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. +The `InventoryItem` data model mainly holds details related to the underlying stock item, but has relations to other data models that include its inventory details. -- [`ProductVariant` data model of Product Module \<> `InventoryItem` data model](#product-module). -- [`InventoryLevel` data model \<> `StockLocation` data model of Stock Location Module](#stock-location-module). (Read-only). +![A diagram showcasing the relation between data models in the Inventory Module](https://res.cloudinary.com/dza7lstvk/image/upload/v1709658103/Medusa%20Resources/inventory-architecture_kxr2ql.png) *** -## Product Module +## InventoryLevel -Each product variant has different inventory details. Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. +An inventory level, represented by the [InventoryLevel data model](https://docs.medusajs.com/references/inventory-next/models/InventoryLevel/index.html.md), holds the inventory and quantity details of an inventory item in a specific location. -![A diagram showcasing an example of how data models from the Inventory and Product Module are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709658720/Medusa%20Resources/inventory-product_ejnray.jpg) +It has three quantity-related properties: -A product variant whose `manage_inventory` property is enabled has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity. +- `stocked_quantity`: The available stock quantity of an item in the associated location. +- `reserved_quantity`: The quantity reserved from the available `stocked_quantity`. It indicates the quantity that's still not removed from stock, but considered as unavailable when checking whether an item is in stock. +- `incoming_quantity`: The incoming stock quantity of an item into the associated location. This property doesn't play into the `stocked_quantity` or when checking whether an item is in stock. -Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md). +### Associated Location -### Retrieve with Query +The inventory level's location is determined by the `location_id` property. Medusa links the `InventoryLevel` data model with the `StockLocation` data model from the Stock Location Module. -To retrieve the product variants of an inventory item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variants.*` in `fields`: +*** -### query.graph +## ReservationItem -```ts -const { data: inventoryItems } = await query.graph({ - entity: "inventory_item", - fields: [ - "variants.*", - ], -}) +A reservation item, represented by the [ReservationItem](https://docs.medusajs.com/references/inventory-next/models/ReservationItem/index.html.md) data model, represents unavailable quantity of an inventory item in a location. It's used when an order is placed but not fulfilled yet. -// inventoryItems.variants -``` +The reserved quantity is associated with a location, so it has a similar relation to that of the `InventoryLevel` with the Stock Location Module. -### useQueryGraphStep -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +# Inventory Module in Medusa Flows -// ... +This document explains how the Inventory Module is used within the Medusa application's flows. -const { data: inventoryItems } = useQueryGraphStep({ - entity: "inventory_item", - fields: [ - "variants.*", - ], -}) +## Product Variant Creation -// inventoryItems.variants -``` +When a product variant is created and its `manage_inventory` property's value is `true`, the Medusa application creates an inventory item associated with that product variant. -### Manage with Link +This flow is implemented within the [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md) -To manage the variants of an inventory item, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +![A diagram showcasing how the Inventory Module is used in the product variant creation form](https://res.cloudinary.com/dza7lstvk/image/upload/v1709661511/Medusa%20Resources/inventory-product-create_khz2hk.jpg) -### link.create +*** -```ts -import { Modules } from "@medusajs/framework/utils" +## Add to Cart -// ... +When a product variant with `manage_inventory` set to `true` is added to cart, the Medusa application checks whether there's sufficient stocked quantity. If not, an error is thrown and the product variant won't be added to the cart. -await link.create({ - [Modules.PRODUCT]: { - variant_id: "variant_123", - }, - [Modules.INVENTORY]: { - inventory_item_id: "iitem_123", - }, -}) -``` +This flow is implemented within the [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md) -### createRemoteLinkStep +![A diagram showcasing how the Inventory Module is used in the add to cart flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1709711645/Medusa%20Resources/inventory-cart-flow_achwq9.jpg) -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +*** -// ... +## Order Placed -createRemoteLinkStep({ - [Modules.PRODUCT]: { - variant_id: "variant_123", - }, - [Modules.INVENTORY]: { - inventory_item_id: "iitem_123", - }, -}) -``` +When an order is placed, the Medusa application creates a reservation item for each product variant with `manage_inventory` set to `true`. -*** +This flow is implemented within the [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md) -## Stock Location Module +![A diagram showcasing how the Inventory Module is used in the order placed flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1709712005/Medusa%20Resources/inventory-order-placed_qdxqdn.jpg) -Medusa defines a read-only link between the `InventoryLevel` data model and the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md)'s `StockLocation` data model. This means you can retrieve the details of an inventory level's stock locations, but you don't manage the links in a pivot table in the database. The stock location of an inventory level is determined by the `location_id` property of the `InventoryLevel` data model. +*** -### Retrieve with Query +## Order Fulfillment -To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`: +When an item in an order is fulfilled and the associated variant has its `manage_inventory` property set to `true`, the Medusa application: -### query.graph +- Subtracts the `reserved_quantity` from the `stocked_quantity` in the inventory level associated with the variant's inventory item. +- Resets the `reserved_quantity` to `0`. +- Deletes the associated reservation item. -```ts -const { data: inventoryLevels } = await query.graph({ - entity: "inventory_level", - fields: [ - "stock_locations.*", - ], -}) +This flow is implemented within the [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md) -// inventoryLevels.stock_locations -``` +![A diagram showcasing how the Inventory Module is used in the order fulfillment flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1709712390/Medusa%20Resources/inventory-order-fulfillment_o9wdxh.jpg) -### useQueryGraphStep +*** -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +## Order Return -// ... +When an item in an order is returned and the associated variant has its `manage_inventory` property set to `true`, the Medusa application increments the `stocked_quantity` of the inventory item's level with the returned quantity. -const { data: inventoryLevels } = useQueryGraphStep({ - entity: "inventory_level", - fields: [ - "stock_locations.*", - ], -}) +This flow is implemented within the [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md) -// inventoryLevels.stock_locations -``` +![A diagram showcasing how the Inventory Module is used in the order return flow](https://res.cloudinary.com/dza7lstvk/image/upload/v1709712457/Medusa%20Resources/inventory-order-return_ihftyk.jpg) +### Dismissed Returned Items -# Order Concepts +If a returned item is considered damaged or is dismissed, its quantity doesn't increment the `stocked_quantity` of the inventory item's level. -In this document, you’ll learn about orders and related concepts -## Order Items +# Inventory Kits -The items purchased in the order are represented by the [OrderItem data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). An order can have multiple items. +In this guide, you'll learn how inventory kits can be used in the Medusa application to support use cases like multi-part products, bundled products, and shared inventory across products. -![A diagram showcasing the relation between an order and its items.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712304722/Medusa%20Resources/order-order-items_uvckxd.jpg) +## What is an Inventory Kit? -### Item’s Product Details +An inventory kit is a collection of inventory items that are linked to a single product variant. These inventory items can be used to represent different parts of a product, or to represent a bundle of products. -The details of the purchased products are represented by the [LineItem data model](https://docs.medusajs.com/references/order/models/OrderLineItem/index.html.md). Not only does a line item hold the details of the product, but also details related to its price, adjustments due to promotions, and taxes. +The Medusa application links inventory items from the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to product variants in the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md). Each variant can have multiple inventory items, and these inventory items can be re-used or shared across variants. -*** +Using inventory kits, you can implement use cases like: -## Order’s Shipping Method +- [Multi-part products](#multi-part-products): A product that consists of multiple parts, each with its own inventory item. +- [Bundled products](#bundled-products): A product that is sold as a bundle, where each variant in the bundle product can re-use the inventory items of another product that should be sold as part of the bundle. -An order has one or more shipping methods used to handle item shipment. +*** -Each shipping method is represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md) that holds its details. The shipping method is linked to the order through the [OrderShipping data model](https://docs.medusajs.com/references/order/models/OrderShipping/index.html.md). +## Multi-Part Products -![A diagram showcasing the relation between an order and its items.](https://res.cloudinary.com/dza7lstvk/image/upload/v1719570409/Medusa%20Resources/order-shipping-method_tkggvd.jpg) +Consider your store sells bicycles that consist of a frame, wheels, and seats, and you want to manage the inventory of these parts separately. -### data Property +To implement this in Medusa, you can: -When fulfilling the order, you can use a third-party fulfillment provider that requires additional custom data to be passed along from the order creation process. +- Create inventory items for each of the different parts. +- For each bicycle product, add a variant whose inventory kit consists of the inventory items of each of the parts. -The `OrderShippingMethod` data model has a `data` property. It’s an object used to store custom data relevant later for fulfillment. +Then, whenever a customer purchases a bicycle, the inventory of each part is updated accordingly. You can also use the `required_quantity` of the variant's inventory items to set how much quantity is consumed of the part's inventory when a bicycle is sold. For example, the bicycle's wheels require 2 wheels inventory items to be sold when a bicycle is sold. -The Medusa application passes the `data` property to the Fulfillment Module when fulfilling items. +![Diagram showcasing how a variant is linked to multi-part inventory items](https://res.cloudinary.com/dza7lstvk/image/upload/v1736414257/Medusa%20Resources/multi-part-product_kepbnx.jpg) -*** +### Create Multi-Part Product -## Order Totals +Using the Medusa Admin, you can create a multi-part product by creating its inventory items first, then assigning these inventory items to the product's variant(s). -The order’s total amounts (including tax total, total after an item is returned, etc…) are represented by the [OrderSummary data model](https://docs.medusajs.com/references/order/models/OrderSummary/index.html.md). +Using [workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), you can implement this by first creating the inventory items: -*** +```ts highlights={multiPartsHighlights1} +import { + createInventoryItemsWorkflow, + useQueryGraphStep, +} from "@medusajs/medusa/core-flows" +import { createWorkflow } from "@medusajs/framework/workflows-sdk" -## Order Payments +export const createMultiPartProductsWorkflow = createWorkflow( + "create-multi-part-products", + () => { + // Alternatively, you can create a stock location + const { data: stockLocations } = useQueryGraphStep({ + entity: "stock_location", + fields: ["*"], + filters: { + name: "European Warehouse", + }, + }) -Payments made on an order, whether they’re capture or refund payments, are recorded as transactions represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md). + const inventoryItems = createInventoryItemsWorkflow.runAsStep({ + input: { + items: [ + { + sku: "FRAME", + title: "Frame", + location_levels: [ + { + stocked_quantity: 100, + location_id: stockLocations[0].id, + }, + ], + }, + { + sku: "WHEEL", + title: "Wheel", + location_levels: [ + { + stocked_quantity: 100, + location_id: stockLocations[0].id, + }, + ], + }, + { + sku: "SEAT", + title: "Seat", + location_levels: [ + { + stocked_quantity: 100, + location_id: stockLocations[0].id, + }, + ], + }, + ], + }, + }) -An order can have multiple transactions. The sum of these transactions must be equal to the order summary’s total. Otherwise, there’s an outstanding amount. + // TODO create the product + } +) +``` -Learn more about transactions in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions/index.html.md). +You start by retrieving the stock location to create the inventory items in. Alternatively, you can [create a stock location](https://docs.medusajs.com/references/medusa-workflows/createStockLocationsWorkflow/index.html.md). +Then, you create the inventory items that the product variant consists of. -# Order Edit +Next, create the product and pass the inventory item's IDs to the product's variant: -In this document, you'll learn about order edits. +```ts highlights={multiPartHighlights2} +import { + // ... + transform, +} from "@medusajs/framework/workflows-sdk" +import { + // ... + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" -## What is an Order Edit? +export const createMultiPartProductsWorkflow = createWorkflow( + "create-multi-part-products", + () => { + // ... -A merchant can edit an order to add new items or change the quantity of existing items in the order. + const inventoryItemIds = transform({ + inventoryItems, + }, (data) => { + return data.inventoryItems.map((inventoryItem) => { + return { + inventory_item_id: inventoryItem.id, + // can also specify required_quantity + } + }) + }) -An order edit is represented by the [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md). + const products = createProductsWorkflow.runAsStep({ + input: { + products: [ + { + title: "Bicycle", + variants: [ + { + title: "Bicycle - Small", + prices: [ + { + amount: 100, + currency_code: "usd", + }, + ], + options: { + "Default Option": "Default Variant", + }, + inventory_items: inventoryItemIds, + }, + ], + options: [ + { + title: "Default Option", + values: ["Default Variant"], + }, + ], + shipping_profile_id: "sp_123", + }, + ], + }, + }) + } +) +``` -The `OrderChange` data model is associated with any type of change, including a return or exchange. However, its `change_type` property distinguishes the type of change it's making. +You prepare the inventory item IDs to pass to the variant using [transform](https://docs.medusajs.com/docs/learn/fundamentals/workflows/variable-manipulation/index.html.md) from the Workflows SDK, then pass these IDs to the created product's variant. -In the case of an order edit, the `OrderChange`'s type is `edit`. +You can now [execute the workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows#3-execute-the-workflow/index.html.md) in [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md), [scheduled jobs](https://docs.medusajs.com/docs/learn/fundamentals/scheduled-jobs/index.html.md), or [subscribers](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md). *** -## Add Items in an Order Edit +## Bundled Products -When the merchant adds new items to the order in the order edit, the item is added as an [OrderItem](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). +Consider you have three products: shirt, pants, and shoes. You sell those products separately, but you also want to offer them as a bundle. -Also, an `OrderChangeAction` is created. The [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md) represents a change made by an `OrderChange`, such as an item added. +![Diagram showcasing products each having their own variants and inventory](https://res.cloudinary.com/dza7lstvk/image/upload/v1736414787/Medusa%20Resources/bundled-product-1_vmzewk.jpg) -So, when an item is added, an `OrderChangeAction` is created with the type `ITEM_ADD`. In its `details` property, the item's ID, price, and quantity are stored. +You can do that by creating a product, where each variant re-uses the inventory items of each of the shirt, pants, and shoes products. -*** +Then, when the bundled product's variant is purchased, the inventory quantity of the associated inventory items are updated. -## Update Items in an Order Edit +![Diagram showcasing a bundled product using the same inventory as the products part of the bundle](https://res.cloudinary.com/dza7lstvk/image/upload/v1736414780/Medusa%20Resources/bundled-product_x94ca1.jpg) -A merchant can update an existing item's quantity or price. +### Create Bundled Product -This change is added as an `OrderChangeAction` with the type `ITEM_UPDATE`. In its `details` property, the item's ID, new price, and new quantity are stored. +You can create a bundled product in the Medusa Admin by creating the products part of the bundle first, each having its own inventory items. Then, you create the bundled product whose variant(s) have inventory kits composed of inventory items from each of the products part of the bundle. -*** +Using [workflows](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md), you can implement this by first creating the products part of the bundle: -## Shipping Methods of New Items in the Edit +```ts highlights={bundledHighlights1} +import { + createWorkflow, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" -Adding new items to the order requires adding shipping methods for those items. +export const createBundledProducts = createWorkflow( + "create-bundled-products", + () => { + const products = createProductsWorkflow.runAsStep({ + input: { + products: [ + { + title: "Shirt", + shipping_profile_id: "sp_123", + variants: [ + { + title: "Shirt", + prices: [ + { + amount: 10, + currency_code: "usd", + }, + ], + options: { + "Default Option": "Default Variant", + }, + manage_inventory: true, + }, + ], + options: [ + { + title: "Default Option", + values: ["Default Variant"], + }, + ], + }, + { + title: "Pants", + shipping_profile_id: "sp_123", + variants: [ + { + title: "Pants", + prices: [ + { + amount: 10, + currency_code: "usd", + }, + ], + options: { + "Default Option": "Default Variant", + }, + manage_inventory: true, + }, + ], + options: [ + { + title: "Default Option", + values: ["Default Variant"], + }, + ], + }, + { + title: "Shoes", + shipping_profile_id: "sp_123", + variants: [ + { + title: "Shoes", + prices: [ + { + amount: 10, + currency_code: "usd", + }, + ], + options: { + "Default Option": "Default Variant", + }, + manage_inventory: true, + }, + ], + options: [ + { + title: "Default Option", + values: ["Default Variant"], + }, + ], + }, + ], + }, + }) -These shipping methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). Also, an `OrderChangeAction` is created with the type `SHIPPING_ADD` + // TODO re-retrieve with inventory + } +) +``` -*** +You create three products and enable `manage_inventory` for their variants, which will create a default inventory item. You can also create the inventory item first for more control over the quantity as explained in [the previous section](#create-multi-part-product). -## How Order Edits Impact an Order’s Version +Next, retrieve the products again but with variant information: -When an order edit is confirmed, the order’s version is incremented. +```ts highlights={bundledHighlights2} +import { + // ... + transform, +} from "@medusajs/framework/workflows-sdk" +import { + useQueryGraphStep, +} from "@medusajs/medusa/core-flows" -*** +export const createBundledProducts = createWorkflow( + "create-bundled-products", + () => { + // ... + const productIds = transform({ + products, + }, (data) => data.products.map((product) => product.id)) -## Payments and Refunds for Order Edit Changes + // @ts-ignore + const { data: productsWithInventory } = useQueryGraphStep({ + entity: "product", + fields: [ + "variants.*", + "variants.inventory_items.*", + ], + filters: { + id: productIds, + }, + }) -Once the Order Edit is confirmed, any additional payment or refund required can be made on the original order. + const inventoryItemIds = transform({ + productsWithInventory, + }, (data) => { + return data.productsWithInventory.map((product) => { + return { + inventory_item_id: product.variants[0].inventory_items?.[0]?.inventory_item_id, + } + }) + }) -This is determined by the comparison between the `OrderSummary` and the order's transactions, as mentioned in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions#checking-outstanding-amount/index.html.md). + // create bundled product + } +) +``` +Using [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), you retrieve the product again with the inventory items of each variant. Then, you prepare the inventory items to pass to the bundled product's variant. -# Links between Order Module and Other Modules +Finally, create the bundled product: -This document showcases the module links defined between the Order Module and other commerce modules. +```ts highlights={bundledProductHighlights3} +export const createBundledProducts = createWorkflow( + "create-bundled-products", + () => { + // ... + const bundledProduct = createProductsWorkflow.runAsStep({ + input: { + products: [ + { + title: "Bundled Clothes", + shipping_profile_id: "sp_123", + variants: [ + { + title: "Bundle", + prices: [ + { + amount: 30, + currency_code: "usd", + }, + ], + options: { + "Default Option": "Default Variant", + }, + inventory_items: inventoryItemIds, + }, + ], + options: [ + { + title: "Default Option", + values: ["Default Variant"], + }, + ], + }, + ], + }, + }).config({ name: "create-bundled-product" }) + } +) +``` + +The bundled product has the same inventory items as those of the products part of the bundle. + +You can now [execute the workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows#3-execute-the-workflow/index.html.md) in [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md), [scheduled jobs](https://docs.medusajs.com/docs/learn/fundamentals/scheduled-jobs/index.html.md), or [subscribers](https://docs.medusajs.com/docs/learn/fundamentals/events-and-subscribers/index.html.md). + + +# Links between Inventory Module and Other Modules + +This document showcases the module links defined between the Inventory Module and other commerce modules. ## Summary -The Order Module has the following links to other modules: +The Inventory Module has the following links to other modules: Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. -- [`Order` data model \<> `Customer` data model of Customer Module](#customer-module). (Read-only). -- [`Order` data model \<> `Cart` data model of Cart Module](#cart-module). -- [`Order` data model \<> `Fulfillment` data model of Fulfillment Module](#fulfillment-module). -- [`Return` data model \<> `Fulfillment` data model of Fulfillment Module](#fulfillment-module). -- [`Order` data model \<> `PaymentCollection` data model of Payment Module](#payment-module). -- [`OrderClaim` data model \<> `PaymentCollection` data model of Payment Module](#payment-module). -- [`OrderExchange` data model \<> `PaymentCollection` data model of Payment Module](#payment-module). -- [`Order` data model \<> `Product` data model of Product Module](#product-module). (Read-only). -- [`Order` data model \<> `Promotion` data model of Promotion Module](#promotion-module). -- [`Order` data model \<> `Region` data model of Region Module](#region-module). (Read-only). -- [`Order` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module). (Read-only). +- [`ProductVariant` data model of Product Module \<> `InventoryItem` data model](#product-module). +- [`InventoryLevel` data model \<> `StockLocation` data model of Stock Location Module](#stock-location-module). (Read-only). *** -## Customer Module +## Product Module -Medusa defines a read-only link between the `Order` data model and the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md)'s `Customer` data model. This means you can retrieve the details of an order's customer, but you don't manage the links in a pivot table in the database. The customer of an order is determined by the `customer_id` property of the `Order` data model. +Each product variant has different inventory details. Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. + +![A diagram showcasing an example of how data models from the Inventory and Product Module are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709658720/Medusa%20Resources/inventory-product_ejnray.jpg) + +A product variant whose `manage_inventory` property is enabled has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity. + +Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md). ### Retrieve with Query -To retrieve the customer of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`: +To retrieve the product variants of an inventory item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variants.*` in `fields`: ### query.graph ```ts -const { data: orders } = await query.graph({ - entity: "order", +const { data: inventoryItems } = await query.graph({ + entity: "inventory_item", fields: [ - "customer.*", + "variants.*", ], }) -// orders.customer +// inventoryItems.variants ``` ### useQueryGraphStep @@ -20797,77 +20551,33 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: orders } = useQueryGraphStep({ - entity: "order", +const { data: inventoryItems } = useQueryGraphStep({ + entity: "inventory_item", fields: [ - "customer.*", + "variants.*", ], }) -// orders.customer +// inventoryItems.variants ``` -*** +### Manage with Link -## Cart Module +To manage the variants of an inventory item, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -The [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md) provides cart-management features. +### link.create -Medusa defines a link between the `Order` and `Cart` data models. The order is linked to the cart used for the purchased. +```ts +import { Modules } from "@medusajs/framework/utils" -![A diagram showcasing an example of how data models from the Cart and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728375735/Medusa%20Resources/cart-order_ijwmfs.jpg) - -### Retrieve with Query - -To retrieve the cart of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`: - -### query.graph - -```ts -const { data: orders } = await query.graph({ - entity: "order", - fields: [ - "cart.*", - ], -}) - -// orders.cart -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... - -const { data: orders } = useQueryGraphStep({ - entity: "order", - fields: [ - "cart.*", - ], -}) - -// orders.cart -``` - -### Manage with Link - -To manage the cart of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): - -### link.create - -```ts -import { Modules } from "@medusajs/framework/utils" - -// ... +// ... await link.create({ - [Modules.ORDER]: { - order_id: "order_123", + [Modules.PRODUCT]: { + variant_id: "variant_123", }, - [Modules.CART]: { - cart_id: "cart_123", + [Modules.INVENTORY]: { + inventory_item_id: "iitem_123", }, }) ``` @@ -20881,44 +20591,36 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.ORDER]: { - order_id: "order_123", + [Modules.PRODUCT]: { + variant_id: "variant_123", }, - [Modules.CART]: { - cart_id: "cart_123", + [Modules.INVENTORY]: { + inventory_item_id: "iitem_123", }, }) ``` *** -## Fulfillment Module - -A fulfillment is created for an orders' items. Medusa defines a link between the `Fulfillment` and `Order` data models. - -![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716549903/Medusa%20Resources/order-fulfillment_h0vlps.jpg) - -A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models. +## Stock Location Module -![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399052/Medusa%20Resources/Social_Media_Graphics_2024_Order_Return_vetimk.jpg) +Medusa defines a read-only link between the `InventoryLevel` data model and the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md)'s `StockLocation` data model. This means you can retrieve the details of an inventory level's stock locations, but you don't manage the links in a pivot table in the database. The stock location of an inventory level is determined by the `location_id` property of the `InventoryLevel` data model. ### Retrieve with Query -To retrieve the fulfillments of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillments.*` in `fields`: - -To retrieve the fulfillments of a return, pass `fulfillments.*` in `fields`. +To retrieve the stock locations of an inventory level with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`: ### query.graph ```ts -const { data: orders } = await query.graph({ - entity: "order", +const { data: inventoryLevels } = await query.graph({ + entity: "inventory_level", fields: [ - "fulfillments.*", + "stock_locations.*", ], }) -// orders.fulfillments +// inventoryLevels.stock_locations ``` ### useQueryGraphStep @@ -20928,343 +20630,222 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: orders } = useQueryGraphStep({ - entity: "order", +const { data: inventoryLevels } = useQueryGraphStep({ + entity: "inventory_level", fields: [ - "fulfillments.*", + "stock_locations.*", ], }) -// orders.fulfillments +// inventoryLevels.stock_locations ``` -### Manage with Link - -To manage the fulfillments of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): - -### link.create -```ts -import { Modules } from "@medusajs/framework/utils" +# Order Concepts -// ... +In this document, you’ll learn about orders and related concepts -await link.create({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.FULFILLMENT]: { - fulfillment_id: "ful_123", - }, -}) -``` +## Order Items -### createRemoteLinkStep +The items purchased in the order are represented by the [OrderItem data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). An order can have multiple items. -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +![A diagram showcasing the relation between an order and its items.](https://res.cloudinary.com/dza7lstvk/image/upload/v1712304722/Medusa%20Resources/order-order-items_uvckxd.jpg) -// ... +### Item’s Product Details -createRemoteLinkStep({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.FULFILLMENT]: { - fulfillment_id: "ful_123", - }, -}) -``` +The details of the purchased products are represented by the [LineItem data model](https://docs.medusajs.com/references/order/models/OrderLineItem/index.html.md). Not only does a line item hold the details of the product, but also details related to its price, adjustments due to promotions, and taxes. *** -## Payment Module +## Order’s Shipping Method -An order's payment details are stored in a payment collection. This also applies for claims and exchanges. +An order has one or more shipping methods used to handle item shipment. -So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models. +Each shipping method is represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md) that holds its details. The shipping method is linked to the order through the [OrderShipping data model](https://docs.medusajs.com/references/order/models/OrderShipping/index.html.md). -![A diagram showcasing an example of how data models from the Order and Payment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716554726/Medusa%20Resources/order-payment_ubdwok.jpg) +![A diagram showcasing the relation between an order and its items.](https://res.cloudinary.com/dza7lstvk/image/upload/v1719570409/Medusa%20Resources/order-shipping-method_tkggvd.jpg) -### Retrieve with Query +### data Property -To retrieve the payment collections of an order, order exchange, or order claim with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_collections.*` in `fields`: +When fulfilling the order, you can use a third-party fulfillment provider that requires additional custom data to be passed along from the order creation process. -### query.graph +The `OrderShippingMethod` data model has a `data` property. It’s an object used to store custom data relevant later for fulfillment. -```ts -const { data: orders } = await query.graph({ - entity: "order", - fields: [ - "payment_collections.*", - ], -}) +The Medusa application passes the `data` property to the Fulfillment Module when fulfilling items. -// orders.payment_collections -``` +*** -### useQueryGraphStep +## Order Totals -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +The order’s total amounts (including tax total, total after an item is returned, etc…) are represented by the [OrderSummary data model](https://docs.medusajs.com/references/order/models/OrderSummary/index.html.md). -// ... +*** -const { data: orders } = useQueryGraphStep({ - entity: "order", - fields: [ - "payment_collections.*", - ], -}) +## Order Payments -// orders.payment_collections -``` +Payments made on an order, whether they’re capture or refund payments, are recorded as transactions represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md). -### Manage with Link +An order can have multiple transactions. The sum of these transactions must be equal to the order summary’s total. Otherwise, there’s an outstanding amount. -To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +Learn more about transactions in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions/index.html.md). -### link.create -```ts -import { Modules } from "@medusajs/framework/utils" +# Order Claim -// ... +In this document, you’ll learn about order claims. -await link.create({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.PAYMENT]: { - payment_collection_id: "paycol_123", - }, -}) -``` +## What is a Claim? -### createRemoteLinkStep +When a customer receives a defective or incorrect item, the merchant can create a claim to refund or replace the item. -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +The [OrderClaim data model](https://docs.medusajs.com/references/order/models/OrderClaim/index.html.md) represents a claim. -// ... +*** -createRemoteLinkStep({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.PAYMENT]: { - payment_collection_id: "paycol_123", - }, -}) -``` +## Claim Type -*** +The `Claim` data model has a `type` property whose value indicates the type of the claim: -## Product Module +- `refund`: the items are returned, and the customer is refunded. +- `replace`: the items are returned, and the customer receives new items. -Medusa defines read-only links between: +*** -- the `OrderLineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `Product` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `OrderLineItem` data model. -- the `OrderLineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `ProductVariant` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `OrderLineItem` data model. +## Old and Replacement Items -### Retrieve with Query +When the claim is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is also created to handle receiving the old items from the customer. -To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`: +Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md). -To retrieve the product, pass `product.*` in `fields`. +If the claim’s type is `replace`, replacement items are represented by the [ClaimItem data model](https://docs.medusajs.com/references/order/models/OrderClaimItem/index.html.md). -### query.graph +*** -```ts -const { data: lineItems } = await query.graph({ - entity: "order_line_item", - fields: [ - "variant.*", - ], -}) +## Claim Shipping Methods -// lineItems.variant -``` +A claim uses shipping methods to send the replacement items to the customer. These methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md). -### useQueryGraphStep +The shipping methods for the returned items are associated with the claim's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md). -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +*** -// ... +## Claim Refund -const { data: lineItems } = useQueryGraphStep({ - entity: "order_line_item", - fields: [ - "variant.*", - ], -}) +If the claim’s type is `refund`, the amount to be refunded is stored in the `refund_amount` property. -// lineItems.variant -``` +The [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md) represents the refunds made for the claim. *** -## Promotion Module +## How Claims Impact an Order’s Version -An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models. +When a claim is confirmed, the order’s version is incremented. -![A diagram showcasing an example of how data models from the Order and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716555015/Medusa%20Resources/order-promotion_dgjzzd.jpg) -### Retrieve with Query +# Order Edit -To retrieve the promotion applied on an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `promotion.*` in `fields`: +In this document, you'll learn about order edits. -### query.graph +## What is an Order Edit? -```ts -const { data: orders } = await query.graph({ - entity: "order", - fields: [ - "promotion.*", - ], -}) +A merchant can edit an order to add new items or change the quantity of existing items in the order. -// orders.promotion -``` +An order edit is represented by the [OrderChange data model](https://docs.medusajs.com/references/order/models/OrderChange/index.html.md). -### useQueryGraphStep +The `OrderChange` data model is associated with any type of change, including a return or exchange. However, its `change_type` property distinguishes the type of change it's making. -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +In the case of an order edit, the `OrderChange`'s type is `edit`. -// ... +*** -const { data: orders } = useQueryGraphStep({ - entity: "order", - fields: [ - "promotion.*", - ], -}) +## Add Items in an Order Edit -// orders.promotion -``` +When the merchant adds new items to the order in the order edit, the item is added as an [OrderItem](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). -### Manage with Link +Also, an `OrderChangeAction` is created. The [OrderChangeAction data model](https://docs.medusajs.com/references/order/models/OrderChangeAction/index.html.md) represents a change made by an `OrderChange`, such as an item added. -To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +So, when an item is added, an `OrderChangeAction` is created with the type `ITEM_ADD`. In its `details` property, the item's ID, price, and quantity are stored. -### link.create +*** -```ts -import { Modules } from "@medusajs/framework/utils" +## Update Items in an Order Edit -// ... +A merchant can update an existing item's quantity or price. -await link.create({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", - }, -}) -``` +This change is added as an `OrderChangeAction` with the type `ITEM_UPDATE`. In its `details` property, the item's ID, new price, and new quantity are stored. -### createRemoteLinkStep +*** -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +## Shipping Methods of New Items in the Edit -// ... +Adding new items to the order requires adding shipping methods for those items. -createRemoteLinkStep({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", - }, -}) -``` +These shipping methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderItem/index.html.md). Also, an `OrderChangeAction` is created with the type `SHIPPING_ADD` *** -## Region Module +## How Order Edits Impact an Order’s Version -Medusa defines a read-only link between the `Order` data model and the [Region Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/region/index.html.md)'s `Region` data model. This means you can retrieve the details of an order's region, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model. +When an order edit is confirmed, the order’s version is incremented. -### Retrieve with Query +*** -To retrieve the region of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`: +## Payments and Refunds for Order Edit Changes -### query.graph +Once the Order Edit is confirmed, any additional payment or refund required can be made on the original order. -```ts -const { data: orders } = await query.graph({ - entity: "order", - fields: [ - "region.*", - ], -}) +This is determined by the comparison between the `OrderSummary` and the order's transactions, as mentioned in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/transactions#checking-outstanding-amount/index.html.md). -// orders.region -``` -### useQueryGraphStep +# Order Exchange -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +In this document, you’ll learn about order exchanges. -// ... +## What is an Exchange? -const { data: orders } = useQueryGraphStep({ - entity: "order", - fields: [ - "region.*", - ], -}) +An exchange is the replacement of an item that the customer ordered with another. -// orders.region -``` +A merchant creates the exchange, specifying the items to be replaced and the new items to be sent. + +The [OrderExchange data model](https://docs.medusajs.com/references/order/models/OrderExchange/index.html.md) represents an exchange. *** -## Sales Channel Module +## Returned and New Items -Medusa defines a read-only link between the `Order` data model and the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md)'s `SalesChannel` data model. This means you can retrieve the details of an order's sales channel, but you don't manage the links in a pivot table in the database. The sales channel of an order is determined by the `sales_channel_id` property of the `Order` data model. +When the exchange is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is created to handle receiving the items back from the customer. -### Retrieve with Query +Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md). -To retrieve the sales channel of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`: +The [OrderExchangeItem data model](https://docs.medusajs.com/references/order/models/OrderExchangeItem/index.html.md) represents the new items to be sent to the customer. -### query.graph +*** -```ts -const { data: orders } = await query.graph({ - entity: "order", - fields: [ - "sales_channel.*", - ], -}) +## Exchange Shipping Methods -// orders.sales_channel -``` +An exchange has shipping methods used to send the new items to the customer. They’re represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md). -### useQueryGraphStep +The shipping methods for the returned items are associated with the exchange's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md). -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +*** -// ... +## Exchange Payment -const { data: orders } = useQueryGraphStep({ - entity: "order", - fields: [ - "sales_channel.*", - ], -}) +The `Exchange` data model has a `difference_due` property that stores the outstanding amount. -// orders.sales_channel -``` +|Condition|Result| +|---|---|---| +|\`difference\_due \< 0\`|Merchant owes the customer a refund of the | +|\`difference\_due > 0\`|Merchant requires additional payment from the customer of the | +|\`difference\_due = 0\`|No payment processing is required.| + +Any payment or refund made is stored in the [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md). + +*** + +## How Exchanges Impact an Order’s Version + +When an exchange is confirmed, the order’s version is incremented. # Order Change @@ -21306,338 +20887,285 @@ The following table lists the possible `action` values that Medusa uses and what |\`WRITE\_OFF\_ITEM\`|Remove an item's quantity as part of the claim, without adding the quantity back to the item variant's inventory.|\`details\`| -# Order Versioning +# Links between Order Module and Other Modules -In this document, you’ll learn how an order and its details are versioned. +This document showcases the module links defined between the Order Module and other commerce modules. -## What's Versioning? +## Summary -Versioning means assigning a version number to a record, such as an order and its items. This is useful to view the different versions of the order following changes in its lifetime. +The Order Module has the following links to other modules: -When changes are made on an order, such as an item is added or returned, the order's version changes. +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. -*** +- [`Order` data model \<> `Customer` data model of Customer Module](#customer-module). (Read-only). +- [`Order` data model \<> `Cart` data model of Cart Module](#cart-module). +- [`Order` data model \<> `Fulfillment` data model of Fulfillment Module](#fulfillment-module). +- [`Return` data model \<> `Fulfillment` data model of Fulfillment Module](#fulfillment-module). +- [`Order` data model \<> `PaymentCollection` data model of Payment Module](#payment-module). +- [`OrderClaim` data model \<> `PaymentCollection` data model of Payment Module](#payment-module). +- [`OrderExchange` data model \<> `PaymentCollection` data model of Payment Module](#payment-module). +- [`Order` data model \<> `Product` data model of Product Module](#product-module). (Read-only). +- [`Order` data model \<> `Promotion` data model of Promotion Module](#promotion-module). +- [`Order` data model \<> `Region` data model of Region Module](#region-module). (Read-only). +- [`Order` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module). (Read-only). -## version Property +*** -The `Order` and `OrderSummary` data models have a `version` property that indicates the current version. By default, its value is `1`. +## Customer Module -Other order-related data models, such as `OrderItem`, also has a `version` property, but it indicates the version it belongs to. +Medusa defines a read-only link between the `Order` data model and the [Customer Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/customer/index.html.md)'s `Customer` data model. This means you can retrieve the details of an order's customer, but you don't manage the links in a pivot table in the database. The customer of an order is determined by the `customer_id` property of the `Order` data model. -*** +### Retrieve with Query -## How the Version Changes +To retrieve the customer of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`: -When the order is changed, such as an item is exchanged, this changes the version of the order and its related data: +### query.graph -1. The version of the order and its summary is incremented. -2. Related order data that have a `version` property, such as the `OrderItem`, are duplicated. The duplicated item has the new version, whereas the original item has the previous version. +```ts +const { data: orders } = await query.graph({ + entity: "order", + fields: [ + "customer.*", + ], +}) -When the order is retrieved, only the related data having the same version is retrieved. +// orders.customer +``` +### useQueryGraphStep -# Promotions Adjustments in Orders +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -In this document, you’ll learn how a promotion is applied to an order’s items and shipping methods using adjustment lines. +// ... -## What are Adjustment Lines? +const { data: orders } = useQueryGraphStep({ + entity: "order", + fields: [ + "customer.*", + ], +}) -An adjustment line indicates a change to a line item or a shipping method’s amount. It’s used to apply promotions or discounts on an order. +// orders.customer +``` -The [OrderLineItemAdjustment data model](https://docs.medusajs.com/references/order/models/OrderLineItemAdjustment/index.html.md) represents changes on a line item, and the [OrderShippingMethodAdjustment data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodAdjustment/index.html.md) represents changes on a shipping method. +*** -![A diagram showcasing the relation between an order, its items and shipping methods, and their adjustment lines](https://res.cloudinary.com/dza7lstvk/image/upload/v1712306017/Medusa%20Resources/order-adjustments_myflir.jpg) +## Cart Module -The `amount` property of the adjustment line indicates the amount to be discounted from the original amount. +The [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md) provides cart-management features. -The ID of the applied promotion is stored in the `promotion_id` property of the adjustment line. +Medusa defines a link between the `Order` and `Cart` data models. The order is linked to the cart used for the purchased. -*** +![A diagram showcasing an example of how data models from the Cart and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728375735/Medusa%20Resources/cart-order_ijwmfs.jpg) -## Discountable Option +### Retrieve with Query -The `OrderLineItem` data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default. +To retrieve the cart of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`: -When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules. +### query.graph -*** +```ts +const { data: orders } = await query.graph({ + entity: "order", + fields: [ + "cart.*", + ], +}) -## Promotion Actions +// orders.cart +``` -When using the Order and Promotion modules together, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods. +### useQueryGraphStep -Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md). - -```ts collapsibleLines="1-10" expandButtonLabel="Show Imports" -import { - ComputeActionAdjustmentLine, - ComputeActionItemLine, - ComputeActionShippingLine, - // ... -} from "@medusajs/framework/types" +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -// retrieve the order -const order = await orderModuleService.retrieveOrder("ord_123", { - relations: [ - "items.item.adjustments", - "shipping_methods.shipping_method.adjustments", +const { data: orders } = useQueryGraphStep({ + entity: "order", + fields: [ + "cart.*", ], }) -// retrieve the line item adjustments -const lineItemAdjustments: ComputeActionItemLine[] = [] -order.items.forEach((item) => { - const filteredAdjustments = item.adjustments?.filter( - (adjustment) => adjustment.code !== undefined - ) as unknown as ComputeActionAdjustmentLine[] - if (filteredAdjustments.length) { - lineItemAdjustments.push({ - ...item, - ...item.detail, - adjustments: filteredAdjustments, - }) - } -}) - -//retrieve shipping method adjustments -const shippingMethodAdjustments: ComputeActionShippingLine[] = - [] -order.shipping_methods.forEach((shippingMethod) => { - const filteredAdjustments = - shippingMethod.adjustments?.filter( - (adjustment) => adjustment.code !== undefined - ) as unknown as ComputeActionAdjustmentLine[] - if (filteredAdjustments.length) { - shippingMethodAdjustments.push({ - ...shippingMethod, - adjustments: filteredAdjustments, - }) - } -}) -// compute actions -const actions = await promotionModuleService.computeActions( - ["promo_123"], - { - items: lineItemAdjustments, - shipping_methods: shippingMethodAdjustments, - // TODO infer from cart or region - currency_code: "usd", - } -) +// orders.cart ``` -The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately. +### Manage with Link -Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the order’s line items and the shipping method’s adjustments. +To manage the cart of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -```ts collapsibleLines="1-9" expandButtonLabel="Show Imports" -import { - AddItemAdjustmentAction, - AddShippingMethodAdjustment, - // ... -} from "@medusajs/framework/types" +### link.create -// ... +```ts +import { Modules } from "@medusajs/framework/utils" -await orderModuleService.setOrderLineItemAdjustments( - order.id, - actions.filter( - (action) => action.action === "addItemAdjustment" - ) as AddItemAdjustmentAction[] -) +// ... -await orderModuleService.setOrderShippingMethodAdjustments( - order.id, - actions.filter( - (action) => - action.action === "addShippingMethodAdjustment" - ) as AddShippingMethodAdjustment[] -) +await link.create({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.CART]: { + cart_id: "cart_123", + }, +}) ``` +### createRemoteLinkStep -# Tax Lines in Order Module - -In this document, you’ll learn about tax lines in an order. - -## What are Tax Lines? - -A tax line indicates the tax rate of a line item or a shipping method. +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -The [OrderLineItemTaxLine data model](https://docs.medusajs.com/references/order/models/OrderLineItemTaxLine/index.html.md) represents a line item’s tax line, and the [OrderShippingMethodTaxLine data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line. +// ... -![A diagram showcasing the relation between orders, items and shipping methods, and tax lines](https://res.cloudinary.com/dza7lstvk/image/upload/v1712307225/Medusa%20Resources/order-tax-lines_sixujd.jpg) +createRemoteLinkStep({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.CART]: { + cart_id: "cart_123", + }, +}) +``` *** -## Tax Inclusivity - -By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount and then adding it to the item/method’s subtotal. - -However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes. +## Fulfillment Module -So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal. +A fulfillment is created for an orders' items. Medusa defines a link between the `Fulfillment` and `Order` data models. -The following diagram is a simplified showcase of how a subtotal is calculated from the tax perspective. +![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716549903/Medusa%20Resources/order-fulfillment_h0vlps.jpg) -![A diagram showcasing how a subtotal is calculated from the tax perspective](https://res.cloudinary.com/dza7lstvk/image/upload/v1712307395/Medusa%20Resources/order-tax-inclusive_oebdnm.jpg) +A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models. -For example, if a line item's amount is `5000`, the tax rate is `10`, and `is_tax_inclusive` is enabled, the tax amount is 10% of `5000`, which is `500`. The item's unit price becomes `4500`. +![A diagram showcasing an example of how data models from the Fulfillment and Order modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399052/Medusa%20Resources/Social_Media_Graphics_2024_Order_Return_vetimk.jpg) +### Retrieve with Query -# Transactions +To retrieve the fulfillments of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillments.*` in `fields`: -In this document, you’ll learn about an order’s transactions and its use. +To retrieve the fulfillments of a return, pass `fulfillments.*` in `fields`. -## What is a Transaction? +### query.graph -A transaction represents any order payment process, such as capturing or refunding an amount. It’s represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md). +```ts +const { data: orders } = await query.graph({ + entity: "order", + fields: [ + "fulfillments.*", + ], +}) -The transaction’s main purpose is to ensure a correct balance between paid and outstanding amounts. +// orders.fulfillments +``` -Transactions are also associated with returns, claims, and exchanges if additional payment or refund is required. +### useQueryGraphStep -*** +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -## Checking Outstanding Amount +// ... -The order’s total amounts are stored in the `OrderSummary`'s `totals` property, which is a JSON object holding the total details of the order. +const { data: orders } = useQueryGraphStep({ + entity: "order", + fields: [ + "fulfillments.*", + ], +}) -```json -{ - "totals": { - "total": 30, - "subtotal": 30, - // ... - } -} +// orders.fulfillments ``` -To check the outstanding amount of the order, its transaction amounts are summed. Then, the following conditions are checked: +### Manage with Link -|Condition|Result| -|---|---|---| -|summary’s total - transaction amounts total = 0|There’s no outstanding amount.| -|summary’s total - transaction amounts total > 0|The customer owes additional payment to the merchant.| -|summary’s total - transaction amounts total \< 0|The merchant owes the customer a refund.| +To manage the fulfillments of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -*** +### link.create -## Transaction Reference +```ts +import { Modules } from "@medusajs/framework/utils" -The Order Module doesn’t provide payment processing functionalities, so it doesn’t store payments that can be processed. Payment functionalities are provided by the [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md). +// ... -The `OrderTransaction` data model has two properties that determine which data model and record holds the actual payment’s details: +await link.create({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_id: "ful_123", + }, +}) +``` -- `reference`: indicates the table’s name in the database. For example, `payment` from the Payment Module. -- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`. +### createRemoteLinkStep +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -# Account Holders and Saved Payment Methods +// ... -In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers. +createRemoteLinkStep({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_id: "ful_123", + }, +}) +``` -Account holders are available starting from Medusa `v2.5.0`. +*** -## What's an Account Holder? +## Payment Module -An account holder represents a customer that can have saved payment methods in a third-party service. It's represented by the `AccountHolder` data model. +An order's payment details are stored in a payment collection. This also applies for claims and exchanges. -It holds fields retrieved from the third-party provider, such as: +So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models. -- `external_id`: The ID of the equivalent customer or account holder in the third-party provider. -- `data`: Data returned by the payment provider when the account holder is created. +![A diagram showcasing an example of how data models from the Order and Payment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716554726/Medusa%20Resources/order-payment_ubdwok.jpg) -A payment provider that supports saving payment methods for customers would create the equivalent of an account holder in the third-party provider. Then, whenever a payment method is saved, it would be saved under the account holder in the third-party provider. +### Retrieve with Query -*** +To retrieve the payment collections of an order, order exchange, or order claim with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_collections.*` in `fields`: -## Save Payment Methods +### query.graph -If a payment provider supports saving payment methods for a customer, they must implement the following methods: +```ts +const { data: orders } = await query.graph({ + entity: "order", + fields: [ + "payment_collections.*", + ], +}) -- `createAccountHolder`: Creates an account holder in the payment provider. The Payment Module uses this method before creating the account holder in Medusa, and uses the returned data to set fields like `external_id` and `data` in the created `AccountHolder` record. -- `deleteAccountHolder`: Deletes an account holder in the payment provider. The Payment Module uses this method when an account holder is deleted in Medusa. -- `savePaymentMethod`: Saves a payment method for an account holder in the payment provider. -- `listPaymentMethods`: Lists saved payment methods in the third-party service for an account holder. This is useful when displaying the customer's saved payment methods in the storefront. +// orders.payment_collections +``` -Learn more about implementing these methods in the [Create Payment Provider guide](https://docs.medusajs.com/references/payment/provider/index.html.md). +### useQueryGraphStep -*** +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -## Account Holder in Medusa Payment Flows +// ... -In the Medusa application, when a payment session is created for a registered customer, the Medusa application uses the Payment Module to create an account holder for the customer. - -Consequently, the Payment Module uses the payment provider to create an account holder in the third-party service, then creates the account holder in Medusa. - -This flow is only supported if the chosen payment provider has implemented the necessary [save payment methods](#save-payment-methods). - - -# Links between Payment Module and Other Modules - -This document showcases the module links defined between the Payment Module and other commerce modules. - -## Summary - -The Payment Module has the following links to other modules: - -- [`Cart` data model of Cart Module \<> `PaymentCollection` data model](#cart-module). -- [`Customer` data model of Customer Module \<> `AccountHolder` data model](#customer-module). -- [`Order` data model of Order Module \<> `PaymentCollection` data model](#order-module). -- [`OrderClaim` data model of Order Module \<> `PaymentCollection` data model](#order-module). -- [`OrderExchange` data model of Order Module \<> `PaymentCollection` data model](#order-module). -- [`Region` data model of Region Module \<> `PaymentProvider` data model](#region-module). - -*** - -## Cart Module - -The Cart Module provides cart-related features, but not payment processing. - -Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart. - -Learn more about this relation in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection#usage-with-the-cart-module/index.html.md). - -### Retrieve with Query - -To retrieve the cart associated with the payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`: - -### query.graph - -```ts -const { data: paymentCollections } = await query.graph({ - entity: "payment_collection", - fields: [ - "cart.*", - ], -}) - -// paymentCollections.cart -``` - -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... - -const { data: paymentCollections } = useQueryGraphStep({ - entity: "payment_collection", +const { data: orders } = useQueryGraphStep({ + entity: "order", fields: [ - "cart.*", + "payment_collections.*", ], }) -// paymentCollections.cart +// orders.payment_collections ``` ### Manage with Link -To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -21647,8 +21175,8 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ - [Modules.CART]: { - cart_id: "cart_123", + [Modules.ORDER]: { + order_id: "order_123", }, [Modules.PAYMENT]: { payment_collection_id: "paycol_123", @@ -21659,13 +21187,14 @@ await link.create({ ### createRemoteLinkStep ```ts +import { Modules } from "@medusajs/framework/utils" import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.CART]: { - cart_id: "cart_123", + [Modules.ORDER]: { + order_id: "order_123", }, [Modules.PAYMENT]: { payment_collection_id: "paycol_123", @@ -21675,27 +21204,30 @@ createRemoteLinkStep({ *** -## Customer Module +## Product Module -Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it. +Medusa defines read-only links between: -This link is available starting from Medusa `v2.5.0`. +- the `OrderLineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `Product` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `OrderLineItem` data model. +- the `OrderLineItem` data model and the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md)'s `ProductVariant` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `OrderLineItem` data model. ### Retrieve with Query -To retrieve the customer associated with an account holder with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`: +To retrieve the variant of a line item with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `variant.*` in `fields`: + +To retrieve the product, pass `product.*` in `fields`. ### query.graph ```ts -const { data: accountHolders } = await query.graph({ - entity: "account_holder", +const { data: lineItems } = await query.graph({ + entity: "order_line_item", fields: [ - "customer.*", + "variant.*", ], }) -// accountHolders.customer +// lineItems.variant ``` ### useQueryGraphStep @@ -21705,79 +21237,39 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: accountHolders } = useQueryGraphStep({ - entity: "account_holder", +const { data: lineItems } = useQueryGraphStep({ + entity: "order_line_item", fields: [ - "customer.*", + "variant.*", ], }) -// accountHolders.customer -``` - -### Manage with Link - -To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): - -### link.create - -```ts -import { Modules } from "@medusajs/framework/utils" - -// ... - -await link.create({ - [Modules.CUSTOMER]: { - customer_id: "cus_123", - }, - [Modules.PAYMENT]: { - account_holder_id: "acchld_123", - }, -}) -``` - -### createRemoteLinkStep - -```ts -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" - -// ... - -createRemoteLinkStep({ - [Modules.CUSTOMER]: { - customer_id: "cus_123", - }, - [Modules.PAYMENT]: { - account_holder_id: "acchld_123", - }, -}) +// lineItems.variant ``` *** -## Order Module - -An order's payment details are stored in a payment collection. This also applies for claims and exchanges. +## Promotion Module -So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models. +An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models. -![A diagram showcasing an example of how data models from the Order and Payment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716554726/Medusa%20Resources/order-payment_ubdwok.jpg) +![A diagram showcasing an example of how data models from the Order and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716555015/Medusa%20Resources/order-promotion_dgjzzd.jpg) ### Retrieve with Query -To retrieve the order of a payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`: +To retrieve the promotion applied on an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `promotion.*` in `fields`: ### query.graph ```ts -const { data: paymentCollections } = await query.graph({ - entity: "payment_collection", +const { data: orders } = await query.graph({ + entity: "order", fields: [ - "order.*", + "promotion.*", ], }) -// paymentCollections.order +// orders.promotion ``` ### useQueryGraphStep @@ -21787,19 +21279,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: paymentCollections } = useQueryGraphStep({ - entity: "payment_collection", +const { data: orders } = useQueryGraphStep({ + entity: "order", fields: [ - "order.*", + "promotion.*", ], }) -// paymentCollections.order +// orders.promotion ``` ### Manage with Link -To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -21812,8 +21304,8 @@ await link.create({ [Modules.ORDER]: { order_id: "order_123", }, - [Modules.PAYMENT]: { - payment_collection_id: "paycol_123", + [Modules.PROMOTION]: { + promotion_id: "promo_123", }, }) ``` @@ -21830,8 +21322,8 @@ createRemoteLinkStep({ [Modules.ORDER]: { order_id: "order_123", }, - [Modules.PAYMENT]: { - payment_collection_id: "paycol_123", + [Modules.PROMOTION]: { + promotion_id: "promo_123", }, }) ``` @@ -21840,27 +21332,23 @@ createRemoteLinkStep({ ## Region Module -You can specify for each region which payment providers are available. The Medusa application defines a link between the `PaymentProvider` and the `Region` data models. - -![A diagram showcasing an example of how resources from the Payment and Region modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711569520/Medusa%20Resources/payment-region_jyo2dz.jpg) - -This increases the flexibility of your store. For example, you only show during checkout the payment providers associated with the cart's region. +Medusa defines a read-only link between the `Order` data model and the [Region Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/region/index.html.md)'s `Region` data model. This means you can retrieve the details of an order's region, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model. ### Retrieve with Query -To retrieve the regions of a payment provider with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `regions.*` in `fields`: +To retrieve the region of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `region.*` in `fields`: ### query.graph ```ts -const { data: paymentProviders } = await query.graph({ - entity: "payment_provider", +const { data: orders } = await query.graph({ + entity: "order", fields: [ - "regions.*", + "region.*", ], }) -// paymentProviders.regions +// orders.region ``` ### useQueryGraphStep @@ -21870,651 +21358,590 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: paymentProviders } = useQueryGraphStep({ - entity: "payment_provider", +const { data: orders } = useQueryGraphStep({ + entity: "order", fields: [ - "regions.*", + "region.*", ], }) -// paymentProviders.regions +// orders.region ``` -### Manage with Link +*** -To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +## Sales Channel Module -### link.create +Medusa defines a read-only link between the `Order` data model and the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md)'s `SalesChannel` data model. This means you can retrieve the details of an order's sales channel, but you don't manage the links in a pivot table in the database. The sales channel of an order is determined by the `sales_channel_id` property of the `Order` data model. -```ts -import { Modules } from "@medusajs/framework/utils" +### Retrieve with Query -// ... +To retrieve the sales channel of an order with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channel.*` in `fields`: -await link.create({ - [Modules.REGION]: { - region_id: "reg_123", - }, - [Modules.PAYMENT]: { - payment_provider_id: "pp_stripe_stripe", - }, +### query.graph + +```ts +const { data: orders } = await query.graph({ + entity: "order", + fields: [ + "sales_channel.*", + ], }) + +// orders.sales_channel ``` -### createRemoteLinkStep +### useQueryGraphStep ```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -createRemoteLinkStep({ - [Modules.REGION]: { - region_id: "reg_123", - }, - [Modules.PAYMENT]: { - payment_provider_id: "pp_stripe_stripe", - }, +const { data: orders } = useQueryGraphStep({ + entity: "order", + fields: [ + "sales_channel.*", + ], }) + +// orders.sales_channel ``` -# Payment Module Options +# Order Versioning -In this document, you'll learn about the options of the Payment Module. +In this document, you’ll learn how an order and its details are versioned. -## All Module Options +## What's Versioning? -|Option|Description|Required|Default| -|---|---|---|---|---|---|---| -|\`webhook\_delay\`|A number indicating the delay in milliseconds before processing a webhook event.|No|\`5000\`| -|\`webhook\_retries\`|The number of times to retry the webhook event processing in case of an error.|No|\`3\`| -|\`providers\`|An array of payment providers to install and register. Learn more |No|-| +Versioning means assigning a version number to a record, such as an order and its items. This is useful to view the different versions of the order following changes in its lifetime. + +When changes are made on an order, such as an item is added or returned, the order's version changes. *** -## providers Option +## version Property -The `providers` option is an array of payment module providers. +The `Order` and `OrderSummary` data models have a `version` property that indicates the current version. By default, its value is `1`. -When the Medusa application starts, these providers are registered and can be used to process payments. +Other order-related data models, such as `OrderItem`, also has a `version` property, but it indicates the version it belongs to. -For example: +*** -```ts title="medusa-config.ts" -import { Modules } from "@medusajs/framework/utils" +## How the Version Changes -// ... +When the order is changed, such as an item is exchanged, this changes the version of the order and its related data: -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/medusa/payment", - options: { - providers: [ - { - resolve: "@medusajs/medusa/payment-stripe", - id: "stripe", - options: { - // ... - }, - }, - ], - }, - }, - ], -}) -``` +1. The version of the order and its summary is incremented. +2. Related order data that have a `version` property, such as the `OrderItem`, are duplicated. The duplicated item has the new version, whereas the original item has the previous version. -The `providers` option is an array of objects that accept the following properties: +When the order is retrieved, only the related data having the same version is retrieved. -- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory. -- `id`: A string indicating the provider's unique name or ID. -- `options`: An optional object of the module provider's options. +# Promotions Adjustments in Orders -# Payment +In this document, you’ll learn how a promotion is applied to an order’s items and shipping methods using adjustment lines. -In this document, you’ll learn what a payment is and how it's created, captured, and refunded. +## What are Adjustment Lines? -## What's a Payment? +An adjustment line indicates a change to a line item or a shipping method’s amount. It’s used to apply promotions or discounts on an order. -When a payment session is authorized, a payment, represented by the [Payment data model](https://docs.medusajs.com/references/payment/models/Payment/index.html.md), is created. This payment can later be captured or refunded. +The [OrderLineItemAdjustment data model](https://docs.medusajs.com/references/order/models/OrderLineItemAdjustment/index.html.md) represents changes on a line item, and the [OrderShippingMethodAdjustment data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodAdjustment/index.html.md) represents changes on a shipping method. -A payment carries many of the data and relations of a payment session: +![A diagram showcasing the relation between an order, its items and shipping methods, and their adjustment lines](https://res.cloudinary.com/dza7lstvk/image/upload/v1712306017/Medusa%20Resources/order-adjustments_myflir.jpg) -- It belongs to the same payment collection. -- It’s associated with the same payment provider, which handles further payment processing. -- It stores the payment session’s `data` property in its `data` property, as it’s still useful for the payment provider’s processing. +The `amount` property of the adjustment line indicates the amount to be discounted from the original amount. -*** +The ID of the applied promotion is stored in the `promotion_id` property of the adjustment line. -## Capture Payments +*** -When a payment is captured, a capture, represented by the [Capture data model](https://docs.medusajs.com/references/payment/models/Capture/index.html.md), is created. It holds details related to the capture, such as the amount, the capture date, and more. +## Discountable Option -The payment can also be captured incrementally, each time a capture record is created for that amount. +The `OrderLineItem` data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default. -![A diagram showcasing how a payment's multiple captures are stored](https://res.cloudinary.com/dza7lstvk/image/upload/v1711565445/Medusa%20Resources/payment-capture_f5fve1.jpg) +When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules. *** -## Refund Payments +## Promotion Actions -When a payment is refunded, a refund, represented by the [Refund data model](https://docs.medusajs.com/references/payment/models/Refund/index.html.md), is created. It holds details related to the refund, such as the amount, refund date, and more. +When using the Order and Promotion modules together, use the [computeActions method of the Promotion Module’s main service](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). It retrieves the actions of line items and shipping methods. -A payment can be refunded multiple times, and each time a refund record is created. +Learn more about actions in the [Promotion Module’s documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/actions/index.html.md). -![A diagram showcasing how a payment's multiple refunds are stored](https://res.cloudinary.com/dza7lstvk/image/upload/v1711565555/Medusa%20Resources/payment-refund_lgfvyy.jpg) +```ts collapsibleLines="1-10" expandButtonLabel="Show Imports" +import { + ComputeActionAdjustmentLine, + ComputeActionItemLine, + ComputeActionShippingLine, + // ... +} from "@medusajs/framework/types" +// ... -# Payment Collection +// retrieve the order +const order = await orderModuleService.retrieveOrder("ord_123", { + relations: [ + "items.item.adjustments", + "shipping_methods.shipping_method.adjustments", + ], +}) +// retrieve the line item adjustments +const lineItemAdjustments: ComputeActionItemLine[] = [] +order.items.forEach((item) => { + const filteredAdjustments = item.adjustments?.filter( + (adjustment) => adjustment.code !== undefined + ) as unknown as ComputeActionAdjustmentLine[] + if (filteredAdjustments.length) { + lineItemAdjustments.push({ + ...item, + ...item.detail, + adjustments: filteredAdjustments, + }) + } +}) -In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module. +//retrieve shipping method adjustments +const shippingMethodAdjustments: ComputeActionShippingLine[] = + [] +order.shipping_methods.forEach((shippingMethod) => { + const filteredAdjustments = + shippingMethod.adjustments?.filter( + (adjustment) => adjustment.code !== undefined + ) as unknown as ComputeActionAdjustmentLine[] + if (filteredAdjustments.length) { + shippingMethodAdjustments.push({ + ...shippingMethod, + adjustments: filteredAdjustments, + }) + } +}) -## What's a Payment Collection? +// compute actions +const actions = await promotionModuleService.computeActions( + ["promo_123"], + { + items: lineItemAdjustments, + shipping_methods: shippingMethodAdjustments, + // TODO infer from cart or region + currency_code: "usd", + } +) +``` -A payment collection stores payment details related to a resource, such as a cart or an order. It’s represented by the [PaymentCollection data model](https://docs.medusajs.com/references/payment/models/PaymentCollection/index.html.md). +The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately. -Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including: +Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the order’s line items and the shipping method’s adjustments. -- The [payment sessions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-session/index.html.md) that represents the payment amount to authorize. -- The [payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md) that are created when a payment session is authorized. They can be captured and refunded. -- The [payment providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md) that handle the processing of each payment session, including the authorization, capture, and refund. +```ts collapsibleLines="1-9" expandButtonLabel="Show Imports" +import { + AddItemAdjustmentAction, + AddShippingMethodAdjustment, + // ... +} from "@medusajs/framework/types" -*** +// ... -## Multiple Payments +await orderModuleService.setOrderLineItemAdjustments( + order.id, + actions.filter( + (action) => action.action === "addItemAdjustment" + ) as AddItemAdjustmentAction[] +) -The payment collection supports multiple payment sessions and payments. +await orderModuleService.setOrderShippingMethodAdjustments( + order.id, + actions.filter( + (action) => + action.action === "addShippingMethodAdjustment" + ) as AddShippingMethodAdjustment[] +) +``` -You can use this to accept payments in increments or split payments across payment providers. -![Diagram showcasing how a payment collection can have multiple payment sessions and payments](https://res.cloudinary.com/dza7lstvk/image/upload/v1711554695/Medusa%20Resources/payment-collection-multiple-payments_oi3z3n.jpg) +# Tax Lines in Order Module -*** +In this document, you’ll learn about tax lines in an order. -## Usage with the Cart Module +## What are Tax Lines? -The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment. +A tax line indicates the tax rate of a line item or a shipping method. -During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing. +The [OrderLineItemTaxLine data model](https://docs.medusajs.com/references/order/models/OrderLineItemTaxLine/index.html.md) represents a line item’s tax line, and the [OrderShippingMethodTaxLine data model](https://docs.medusajs.com/references/order/models/OrderShippingMethodTaxLine/index.html.md) represents a shipping method’s tax line. -It also implements the payment flow during checkout as explained in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-flow/index.html.md). +![A diagram showcasing the relation between orders, items and shipping methods, and tax lines](https://res.cloudinary.com/dza7lstvk/image/upload/v1712307225/Medusa%20Resources/order-tax-lines_sixujd.jpg) -![Diagram showcasing the relation between the Payment and Cart modules](https://res.cloudinary.com/dza7lstvk/image/upload/v1711537849/Medusa%20Resources/cart-payment_ixziqm.jpg) +*** +## Tax Inclusivity -# Accept Payment Flow +By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount and then adding it to the item/method’s subtotal. -In this document, you’ll learn how to implement an accept-payment flow using workflows or the Payment Module's main service. +However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes. -It's highly recommended to use Medusa's workflows to implement this flow. Use the Payment Module's main service for more complex cases. +So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal. -For a guide on how to implement this flow in the storefront, check out [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/index.html.md). +The following diagram is a simplified showcase of how a subtotal is calculated from the tax perspective. -## Flow Overview +![A diagram showcasing how a subtotal is calculated from the tax perspective](https://res.cloudinary.com/dza7lstvk/image/upload/v1712307395/Medusa%20Resources/order-tax-inclusive_oebdnm.jpg) -![A diagram showcasing the payment flow's steps](https://res.cloudinary.com/dza7lstvk/image/upload/v1711566781/Medusa%20Resources/payment-flow_jblrvw.jpg) +For example, if a line item's amount is `5000`, the tax rate is `10`, and `is_tax_inclusive` is enabled, the tax amount is 10% of `5000`, which is `500`. The item's unit price becomes `4500`. -*** -## 1. Create a Payment Collection +# Order Return -A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection. +In this document, you’ll learn about order returns. -For example: +## What is a Return? -### Using Workflow +A return is the return of items delivered from the customer back to the merchant. It is represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md). -```ts -import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows" +A return is requested either by the customer from the storefront, or the merchant from the admin. Medusa supports an automated Return Merchandise Authorization (RMA) flow. -// ... +![Diagram showcasing the automated RMA flow.](https://res.cloudinary.com/dza7lstvk/image/upload/v1719578128/Medusa%20Resources/return-rma_pzprwq.jpg) -await createPaymentCollectionForCartWorkflow(req.scope) - .run({ - input: { - cart_id: "cart_123", - }, - }) -``` +Once the merchant receives the returned items, they mark the return as received. -### Using Service +*** -```ts -const paymentCollection = - await paymentModuleService.createPaymentCollections({ - currency_code: "usd", - amount: 5000, - }) -``` +## Returned Items -*** +The items to be returned are represented by the [ReturnItem data model](references/order/models/ReturnItem). -## 2. Create Payment Sessions +The `ReturnItem` model has two properties storing the item's quantity: -The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider. +1. `received_quantity`: The quantity of the item that's received and can be added to the item's inventory quantity. +2. `damaged_quantity`: The quantity of the item that's damaged, meaning it can't be sold again or added to the item's inventory quantity. -So, after creating the payment collection, create at least one payment session for a provider. +*** -For example: +## Return Shipping Methods -### Using Workflow +A return has shipping methods used to return the items to the merchant. The shipping methods are represented by the [OrderShippingMethod data model](references/order/models/OrderShippingMethod). -```ts -import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows" +In the Medusa application, the shipping method for a return is created only from a shipping option, provided by the Fulfillment Module, that has the rule `is_return` enabled. -// ... +*** -const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope) - .run({ - input: { - payment_collection_id: "paycol_123", - provider_id: "stripe", - }, - }) -``` +## Refund Payment -### Using Service +The `refund_amount` property of the `Return` data model holds the amount a merchant must refund the customer. -```ts -const paymentSession = - await paymentModuleService.createPaymentSession( - paymentCollection.id, - { - provider_id: "stripe", - currency_code: "usd", - amount: 5000, - data: { - // any necessary data for the - // payment provider - }, - } - ) -``` +The [OrderTransaction data model](references/order/models/OrderTransaction) represents the refunds made for the return. *** -## 3. Authorize Payment Session +## Returns in Exchanges and Claims -Once the customer chooses a payment session, start the authorization process. This may involve some action performed by the third-party payment provider, such as entering a 3DS code. +When a merchant creates an exchange or a claim, it includes returning items from the customer. -For example: +The `Return` data model also represents the return of these items. In this case, the return is associated with the exchange or claim it was created for. -### Using Step +*** -```ts -import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows" +## How Returns Impact an Order’s Version -// ... +The order’s version is incremented when: -authorizePaymentSessionStep({ - id: "payses_123", - context: {}, -}) -``` +1. A return is requested. +2. A return is marked as received. -### Using Service -```ts -const payment = authorizePaymentSessionStep({ - id: "payses_123", - context: {}, -}) -``` +# Transactions -When the payment authorization is successful, a payment is created and returned. +In this document, you’ll learn about an order’s transactions and its use. -### Handling Additional Action +## What is a Transaction? -If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step. +A transaction represents any order payment process, such as capturing or refunding an amount. It’s represented by the [OrderTransaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md). -If the payment authorization isn’t successful, whether because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error. +The transaction’s main purpose is to ensure a correct balance between paid and outstanding amounts. -In that case, you can catch that error and, if the session's `status` property is `requires_more`, handle the additional action, then retry the authorization. +Transactions are also associated with returns, claims, and exchanges if additional payment or refund is required. -For example: +*** -```ts -try { - const payment = - await paymentModuleService.authorizePaymentSession( - paymentSession.id, - {} - ) -} catch (e) { - // retrieve the payment session again - const updatedPaymentSession = ( - await paymentModuleService.listPaymentSessions({ - id: [paymentSession.id], - }) - )[0] +## Checking Outstanding Amount - if (updatedPaymentSession.status === "requires_more") { - // TODO perform required action - // TODO authorize payment again. +The order’s total amounts are stored in the `OrderSummary`'s `totals` property, which is a JSON object holding the total details of the order. + +```json +{ + "totals": { + "total": 30, + "subtotal": 30, + // ... } } ``` -*** - -## 4. Payment Flow Complete - -The payment flow is complete once the payment session is authorized and the payment is created. - -You can then: - -- Capture the payment either using the [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md) or [capturePayment method](https://docs.medusajs.com/references/payment/capturePayment/index.html.md). -- Refund captured amounts using the [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) or [refundPayment method](https://docs.medusajs.com/references/payment/refundPayment/index.html.md). - -Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually. - - -# Order Exchange - -In this document, you’ll learn about order exchanges. - -## What is an Exchange? - -An exchange is the replacement of an item that the customer ordered with another. - -A merchant creates the exchange, specifying the items to be replaced and the new items to be sent. +To check the outstanding amount of the order, its transaction amounts are summed. Then, the following conditions are checked: -The [OrderExchange data model](https://docs.medusajs.com/references/order/models/OrderExchange/index.html.md) represents an exchange. +|Condition|Result| +|---|---|---| +|summary’s total - transaction amounts total = 0|There’s no outstanding amount.| +|summary’s total - transaction amounts total > 0|The customer owes additional payment to the merchant.| +|summary’s total - transaction amounts total \< 0|The merchant owes the customer a refund.| *** -## Returned and New Items - -When the exchange is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is created to handle receiving the items back from the customer. - -Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md). +## Transaction Reference -The [OrderExchangeItem data model](https://docs.medusajs.com/references/order/models/OrderExchangeItem/index.html.md) represents the new items to be sent to the customer. +The Order Module doesn’t provide payment processing functionalities, so it doesn’t store payments that can be processed. Payment functionalities are provided by the [Payment Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/index.html.md). -*** +The `OrderTransaction` data model has two properties that determine which data model and record holds the actual payment’s details: -## Exchange Shipping Methods +- `reference`: indicates the table’s name in the database. For example, `payment` from the Payment Module. +- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`. -An exchange has shipping methods used to send the new items to the customer. They’re represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md). -The shipping methods for the returned items are associated with the exchange's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md). +# Pricing Concepts -*** +In this document, you’ll learn about the main concepts in the Pricing Module. -## Exchange Payment +## Price Set -The `Exchange` data model has a `difference_due` property that stores the outstanding amount. +A [PriceSet](https://docs.medusajs.com/references/pricing/models/PriceSet/index.html.md) represents a collection of prices that are linked to a resource (for example, a product or a shipping option). -|Condition|Result| -|---|---|---| -|\`difference\_due \< 0\`|Merchant owes the customer a refund of the | -|\`difference\_due > 0\`|Merchant requires additional payment from the customer of the | -|\`difference\_due = 0\`|No payment processing is required.| +Each of these prices are represented by the [Price data module](https://docs.medusajs.com/references/pricing/models/Price/index.html.md). -Any payment or refund made is stored in the [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md). +![A diagram showcasing the relation between the price set and price](https://res.cloudinary.com/dza7lstvk/image/upload/v1709648650/Medusa%20Resources/price-set-money-amount_xeees0.jpg) *** -## How Exchanges Impact an Order’s Version +## Price List -When an exchange is confirmed, the order’s version is incremented. +A [PriceList](https://docs.medusajs.com/references/pricing/models/PriceList/index.html.md) is a group of prices only enabled if their conditions and rules are satisfied. +A price list has optional `start_date` and `end_date` properties that indicate the date range in which a price list can be applied. -# Order Return +Its associated prices are represented by the `Price` data model. -In this document, you’ll learn about order returns. -## What is a Return? +# Price Rules -A return is the return of items delivered from the customer back to the merchant. It is represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md). +In this document, you'll learn about price rules for price sets and price lists. -A return is requested either by the customer from the storefront, or the merchant from the admin. Medusa supports an automated Return Merchandise Authorization (RMA) flow. +## Price Rule -![Diagram showcasing the automated RMA flow.](https://res.cloudinary.com/dza7lstvk/image/upload/v1719578128/Medusa%20Resources/return-rma_pzprwq.jpg) +You can restrict prices by rules. Each rule of a price is represented by the [PriceRule data model](https://docs.medusajs.com/references/pricing/models/PriceRule/index.html.md). -Once the merchant receives the returned items, they mark the return as received. +The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price. -*** +For exmaple, you create a price restricted to `10557` zip codes. -## Returned Items +![A diagram showcasing the relation between the PriceRule and Price](https://res.cloudinary.com/dza7lstvk/image/upload/v1709648772/Medusa%20Resources/price-rule-1_vy8bn9.jpg) -The items to be returned are represented by the [ReturnItem data model](references/order/models/ReturnItem). +A price can have multiple price rules. -The `ReturnItem` model has two properties storing the item's quantity: +For example, a price can be restricted by a region and a zip code. -1. `received_quantity`: The quantity of the item that's received and can be added to the item's inventory quantity. -2. `damaged_quantity`: The quantity of the item that's damaged, meaning it can't be sold again or added to the item's inventory quantity. +![A diagram showcasing the relation between the PriceRule and Price with multiple rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709649296/Medusa%20Resources/price-rule-3_pwpocz.jpg) *** -## Return Shipping Methods +## Price List Rules -A return has shipping methods used to return the items to the merchant. The shipping methods are represented by the [OrderShippingMethod data model](references/order/models/OrderShippingMethod). +Rules applied to a price list are represented by the [PriceListRule data model](https://docs.medusajs.com/references/pricing/models/PriceListRule/index.html.md). -In the Medusa application, the shipping method for a return is created only from a shipping option, provided by the Fulfillment Module, that has the rule `is_return` enabled. +The `rules_count` property of a `PriceList` indicates how many rules are applied to it. -*** +![A diagram showcasing the relation between the PriceSet, PriceList, Price, RuleType, and PriceListRuleValue](https://res.cloudinary.com/dza7lstvk/image/upload/v1709641999/Medusa%20Resources/price-list_zd10yd.jpg) -## Refund Payment -The `refund_amount` property of the `Return` data model holds the amount a merchant must refund the customer. +# Prices Calculation -The [OrderTransaction data model](references/order/models/OrderTransaction) represents the refunds made for the return. +In this document, you'll learn how prices are calculated when you use the [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) of the Pricing Module's main service. -*** +## calculatePrices Method -## Returns in Exchanges and Claims +The [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) accepts as parameters the ID of one or more price sets and a context. -When a merchant creates an exchange or a claim, it includes returning items from the customer. +It returns a price object with the best matching price for each price set. -The `Return` data model also represents the return of these items. In this case, the return is associated with the exchange or claim it was created for. +### Calculation Context -*** +The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set. -## How Returns Impact an Order’s Version +For example: -The order’s version is incremented when: +```ts +const price = await pricingModuleService.calculatePrices( + { id: [priceSetId] }, + { + context: { + currency_code: currencyCode, + region_id: "reg_123", + }, + } +) +``` -1. A return is requested. -2. A return is marked as received. +In this example, you retrieve the prices in a price set for the specified currency code and region ID. +### Returned Price Object -# Order Claim +For each price set, the `calculatePrices` method selects two prices: -In this document, you’ll learn about order claims. +- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price. +- An original price, which is either: + - The same price as the calculated price if the price list it belongs to is of type `override`; + - Or a price that doesn't belong to a price list and best matches the specified context. -## What is a Claim? +Both prices are returned in an object that has the following properties: -When a customer receives a defective or incorrect item, the merchant can create a claim to refund or replace the item. +- id: (\`string\`) The ID of the price set from which the price was selected. +- is\_calculated\_price\_price\_list: (\`boolean\`) Whether the calculated price belongs to a price list. +- calculated\_amount: (\`number\`) The amount of the calculated price, or \`null\` if there isn't a calculated price. This is the amount shown to the customer. +- is\_original\_price\_price\_list: (\`boolean\`) Whether the original price belongs to a price list. +- original\_amount: (\`number\`) The amount of the original price, or \`null\` if there isn't an original price. This amount is useful to compare with the \`calculated\_amount\`, such as to check for discounted value. +- currency\_code: (\`string\`) The currency code of the calculated price, or \`null\` if there isn't a calculated price. +- is\_calculated\_price\_tax\_inclusive: (\`boolean\`) Whether the calculated price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx) +- is\_original\_price\_tax\_inclusive: (\`boolean\`) Whether the original price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx) +- calculated\_price: (\`object\`) The calculated price's price details. -The [OrderClaim data model](https://docs.medusajs.com/references/order/models/OrderClaim/index.html.md) represents a claim. + - id: (\`string\`) The ID of the price. -*** + - price\_list\_id: (\`string\`) The ID of the associated price list. -## Claim Type + - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`. -The `Claim` data model has a `type` property whose value indicates the type of the claim: + - min\_quantity: (\`number\`) The price's min quantity condition. -- `refund`: the items are returned, and the customer is refunded. -- `replace`: the items are returned, and the customer receives new items. + - max\_quantity: (\`number\`) The price's max quantity condition. +- original\_price: (\`object\`) The original price's price details. -*** + - id: (\`string\`) The ID of the price. -## Old and Replacement Items + - price\_list\_id: (\`string\`) The ID of the associated price list. -When the claim is created, a return, represented by the [Return data model](https://docs.medusajs.com/references/order/models/Return/index.html.md), is also created to handle receiving the old items from the customer. + - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`. -Learn more about returns in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return/index.html.md). + - min\_quantity: (\`number\`) The price's min quantity condition. -If the claim’s type is `replace`, replacement items are represented by the [ClaimItem data model](https://docs.medusajs.com/references/order/models/OrderClaimItem/index.html.md). + - max\_quantity: (\`number\`) The price's max quantity condition. *** -## Claim Shipping Methods +## Examples -A claim uses shipping methods to send the replacement items to the customer. These methods are represented by the [OrderShippingMethod data model](https://docs.medusajs.com/references/order/models/OrderShippingMethod/index.html.md). +Consider the following price set: -The shipping methods for the returned items are associated with the claim's return, as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/return#return-shipping-methods/index.html.md). - -*** - -## Claim Refund - -If the claim’s type is `refund`, the amount to be refunded is stored in the `refund_amount` property. - -The [Transaction data model](https://docs.medusajs.com/references/order/models/OrderTransaction/index.html.md) represents the refunds made for the claim. - -*** - -## How Claims Impact an Order’s Version - -When a claim is confirmed, the order’s version is incremented. - - -# Payment Module Provider - -In this document, you’ll learn what a payment module provider is. - -## What's a Payment Module Provider? - -A payment module provider registers a payment provider that handles payment processing in the Medusa application. It integrates third-party payment providers, such as Stripe. - -To authorize a payment amount with a payment provider, a payment session is created and associated with that payment provider. The payment provider is then used to handle the authorization. - -After the payment session is authorized, the payment provider is associated with the resulting payment and handles its payment processing, such as to capture or refund payment. - -### List of Payment Module Providers - -- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md) - -*** - -## System Payment Provider - -The Payment Module provides a `system` payment provider that acts as a placeholder payment provider. - -It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method. - -*** - -## How are Payment Providers Created? - -A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`. - -Refer to [this guide](https://docs.medusajs.com/references/payment/provider/index.html.md) on how to create a payment provider for the Payment Module. - -*** - -## Configure Payment Providers - -The Payment Module accepts a `providers` option that allows you to register providers in your application. - -Learn more about this option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options#providers/index.html.md). - -*** - -## PaymentProvider Data Model - -When the Medusa application starts and registers the payment providers, it also creates a record of the `PaymentProvider` data model if none exists. - -This data model is used to reference a payment provider and determine whether it’s installed in the application. - - -# Webhook Events - -In this document, you’ll learn how the Payment Module supports listening to webhook events. - -## What's a Webhook Event? - -A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status. - -This is useful in many cases such as when a payment is being processed asynchronously or when a request is interrupted and the payment provider is sending details on the process later. - -*** - -## getWebhookActionAndData Method - -The Payment Module’s main service has a [getWebhookActionAndData method](https://docs.medusajs.com/references/payment/getWebhookActionAndData/index.html.md) used to handle incoming webhook events from third-party payment services. The method delegates the handling to the associated payment provider, which returns the event's details. - -Medusa implements a webhook listener route at the `/hooks/payment/[identifier]_[provider]` API route, where: - -- `[identifier]` is the `identifier` static property defined in the payment provider. For example, `stripe`. -- `[provider]` is the ID of the provider. For example, `stripe`. - -For example, when integrating basic Stripe payments with the [Stripe Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md), the webhook listener route is `/hooks/payment/stripe_stripe`. If you're integrating Stripe's Bancontact payments, the webhook listener route is `/hooks/payment/stripe-bancontact_stripe`. - -Use that webhook listener in your third-party payment provider's configurations. - -![A diagram showcasing the steps of how the getWebhookActionAndData method words](https://res.cloudinary.com/dza7lstvk/image/upload/v1711567415/Medusa%20Resources/payment-webhook_seaocg.jpg) - -If the event's details indicate that the payment should be authorized, then the [authorizePaymentSession method of the main service](https://docs.medusajs.com/references/payment/authorizePaymentSession/index.html.md) is executed on the specified payment session. - -If the event's details indicate that the payment should be captured, then the [capturePayment method of the main service](https://docs.medusajs.com/references/payment/capturePayment/index.html.md) is executed on the payment of the specified payment session. - -### Actions After Webhook Payment Processing - -After the payment webhook actions are processed and the payment is authorized or captured, the Medusa application completes the cart associated with the payment's collection if it's not completed yet. - - -# Payment Session - -In this document, you’ll learn what a payment session is. - -## What's a Payment Session? - -A payment session, represented by the [PaymentSession data model](https://docs.medusajs.com/references/payment/models/PaymentSession/index.html.md), is a payment amount to be authorized. It’s associated with a payment provider that handles authorizing it. - -A payment collection can have multiple payment sessions. Using this feature, you can implement payment in installments or payments using multiple providers. - -![Diagram showcasing how every payment session has a different payment provider](https://res.cloudinary.com/dza7lstvk/image/upload/v1711565056/Medusa%20Resources/payment-session-provider_guxzqt.jpg) - -*** - -## data Property - -Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data. - -For example, the customer's ID in Stripe is stored in the `data` property. - -*** - -## Payment Session Status - -The `status` property of a payment session indicates its current status. Its value can be: +```ts +const priceSet = await pricingModuleService.createPriceSets({ + prices: [ + // default price + { + amount: 500, + currency_code: "EUR", + rules: {}, + }, + // prices with rules + { + amount: 400, + currency_code: "EUR", + rules: { + region_id: "reg_123", + }, + }, + { + amount: 450, + currency_code: "EUR", + rules: { + city: "krakow", + }, + }, + { + amount: 500, + currency_code: "EUR", + rules: { + city: "warsaw", + region_id: "reg_123", + }, + }, + ], +}) +``` -- `pending`: The payment session is awaiting authorization. -- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code. -- `authorized`: The payment session is authorized. -- `error`: An error occurred while authorizing the payment. -- `canceled`: The authorization of the payment session has been canceled. +### Default Price Selection +### Code -# Pricing Concepts +```ts +const price = await pricingModuleService.calculatePrices( + { id: [priceSet.id] }, + { + context: { + currency_code: "EUR" + } + } +) +``` -In this document, you’ll learn about the main concepts in the Pricing Module. +### Result -## Price Set +### Calculate Prices with Rules -A [PriceSet](https://docs.medusajs.com/references/pricing/models/PriceSet/index.html.md) represents a collection of prices that are linked to a resource (for example, a product or a shipping option). +### Code -Each of these prices are represented by the [Price data module](https://docs.medusajs.com/references/pricing/models/Price/index.html.md). +```ts +const price = await pricingModuleService.calculatePrices( + { id: [priceSet.id] }, + { + context: { + currency_code: "EUR", + region_id: "reg_123", + city: "krakow" + } + } +) +``` -![A diagram showcasing the relation between the price set and price](https://res.cloudinary.com/dza7lstvk/image/upload/v1709648650/Medusa%20Resources/price-set-money-amount_xeees0.jpg) +### Result -*** +### Price Selection with Price List -## Price List +### Code -A [PriceList](https://docs.medusajs.com/references/pricing/models/PriceList/index.html.md) is a group of prices only enabled if their conditions and rules are satisfied. +```ts +const priceList = pricingModuleService.createPriceLists([{ + title: "Summer Price List", + description: "Price list for summer sale", + starts_at: Date.parse("01/10/2023").toString(), + ends_at: Date.parse("31/10/2023").toString(), + rules: { + region_id: ['PL'] + }, + type: "sale", + prices: [ + { + amount: 400, + currency_code: "EUR", + price_set_id: priceSet.id, + }, + { + amount: 450, + currency_code: "EUR", + price_set_id: priceSet.id, + }, + ], +}]); -A price list has optional `start_date` and `end_date` properties that indicate the date range in which a price list can be applied. +const price = await pricingModuleService.calculatePrices( + { id: [priceSet.id] }, + { + context: { + currency_code: "EUR", + region_id: "PL", + city: "krakow" + } + } +) +``` -Its associated prices are represented by the `Price` data model. +### Result # Links between Pricing Module and Other Modules @@ -22699,498 +22126,400 @@ createRemoteLinkStep({ ``` -# Prices Calculation +# Tax-Inclusive Pricing -In this document, you'll learn how prices are calculated when you use the [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) of the Pricing Module's main service. +In this document, you’ll learn about tax-inclusive pricing and how it's used when calculating prices. -## calculatePrices Method +## What is Tax-Inclusive Pricing? -The [calculatePrices method](https://docs.medusajs.com/references/pricing/calculatePrices/index.html.md) accepts as parameters the ID of one or more price sets and a context. +A tax-inclusive price is a price of a resource that includes taxes. Medusa calculates the tax amount from the price rather than adds the amount to it. -It returns a price object with the best matching price for each price set. +For example, if a product’s price is $50, the tax rate is 2%, and tax-inclusive pricing is enabled, then the product's price is $49, and the applied tax amount is $1. -### Calculation Context +*** -The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set. +## How is Tax-Inclusive Pricing Set? -For example: +The [PricePreference data model](https://docs.medusajs.com/references/pricing/PricePreference/index.html.md) holds the tax-inclusive setting for a context. It has two properties that indicate the context: -```ts -const price = await pricingModuleService.calculatePrices( - { id: [priceSetId] }, - { - context: { - currency_code: currencyCode, - region_id: "reg_123", - }, - } -) -``` +- `attribute`: The name of the attribute to compare against. For example, `region_id` or `currency_code`. +- `value`: The attribute’s value. For example, `reg_123` or `usd`. -In this example, you retrieve the prices in a price set for the specified currency code and region ID. +Only `region_id` and `currency_code` are supported as an `attribute` at the moment. -### Returned Price Object +The `is_tax_inclusive` property indicates whether tax-inclusivity is enabled in the specified context. -For each price set, the `calculatePrices` method selects two prices: +For example: -- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price. -- An original price, which is either: - - The same price as the calculated price if the price list it belongs to is of type `override`; - - Or a price that doesn't belong to a price list and best matches the specified context. +```json +{ + "attribute": "currency_code", + "value": "USD", + "is_tax_inclusive": true, +} +``` -Both prices are returned in an object that has the following properties: +In this example, tax-inclusivity is enabled for the `USD` currency code. -- id: (\`string\`) The ID of the price set from which the price was selected. -- is\_calculated\_price\_price\_list: (\`boolean\`) Whether the calculated price belongs to a price list. -- calculated\_amount: (\`number\`) The amount of the calculated price, or \`null\` if there isn't a calculated price. This is the amount shown to the customer. -- is\_original\_price\_price\_list: (\`boolean\`) Whether the original price belongs to a price list. -- original\_amount: (\`number\`) The amount of the original price, or \`null\` if there isn't an original price. This amount is useful to compare with the \`calculated\_amount\`, such as to check for discounted value. -- currency\_code: (\`string\`) The currency code of the calculated price, or \`null\` if there isn't a calculated price. -- is\_calculated\_price\_tax\_inclusive: (\`boolean\`) Whether the calculated price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx) -- is\_original\_price\_tax\_inclusive: (\`boolean\`) Whether the original price is tax inclusive. Learn more about tax-inclusivity in \[this document]\(../tax-inclusive-pricing/page.mdx) -- calculated\_price: (\`object\`) The calculated price's price details. +*** - - id: (\`string\`) The ID of the price. +## Tax-Inclusive Pricing in Price Calculation - - price\_list\_id: (\`string\`) The ID of the associated price list. +### Tax Context - - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`. +As mentioned in the [Price Calculation documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), The `calculatePrices` method accepts as a parameter a calculation context. - - min\_quantity: (\`number\`) The price's min quantity condition. +To get accurate tax results, pass the `region_id` and / or `currency_code` in the calculation context. - - max\_quantity: (\`number\`) The price's max quantity condition. -- original\_price: (\`object\`) The original price's price details. +### Returned Tax Properties - - id: (\`string\`) The ID of the price. +The `calculatePrices` method returns two properties related to tax-inclusivity: - - price\_list\_id: (\`string\`) The ID of the associated price list. +Learn more about the returned properties in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md). - - price\_list\_type: (\`string\`) The price list's type. For example, \`sale\`. +- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive. +- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive. - - min\_quantity: (\`number\`) The price's min quantity condition. +A price is considered tax-inclusive if: - - max\_quantity: (\`number\`) The price's max quantity condition. +1. It belongs to the region or currency code specified in the calculation context; +2. and the region or currency code has a price preference with `is_tax_inclusive` enabled. -*** +### Tax Context Precedence -## Examples +A region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive if: -Consider the following price set: +- both the `region_id` and `currency_code` are provided in the calculation context; +- the selected price belongs to the region; +- and the region has a price preference -```ts -const priceSet = await pricingModuleService.createPriceSets({ - prices: [ - // default price - { - amount: 500, - currency_code: "EUR", - rules: {}, - }, - // prices with rules - { - amount: 400, - currency_code: "EUR", - rules: { - region_id: "reg_123", - }, - }, - { - amount: 450, - currency_code: "EUR", - rules: { - city: "krakow", - }, - }, - { - amount: 500, - currency_code: "EUR", - rules: { - city: "warsaw", - region_id: "reg_123", - }, - }, - ], -}) -``` -### Default Price Selection +# Account Holders and Saved Payment Methods -### Code +In this documentation, you'll learn about account holders, and how they're used to save payment methods in third-party payment providers. -```ts -const price = await pricingModuleService.calculatePrices( - { id: [priceSet.id] }, - { - context: { - currency_code: "EUR" - } - } -) -``` +Account holders are available starting from Medusa `v2.5.0`. -### Result +## What's an Account Holder? -### Calculate Prices with Rules +An account holder represents a customer that can have saved payment methods in a third-party service. It's represented by the `AccountHolder` data model. -### Code +It holds fields retrieved from the third-party provider, such as: -```ts -const price = await pricingModuleService.calculatePrices( - { id: [priceSet.id] }, - { - context: { - currency_code: "EUR", - region_id: "reg_123", - city: "krakow" - } - } -) -``` +- `external_id`: The ID of the equivalent customer or account holder in the third-party provider. +- `data`: Data returned by the payment provider when the account holder is created. -### Result +A payment provider that supports saving payment methods for customers would create the equivalent of an account holder in the third-party provider. Then, whenever a payment method is saved, it would be saved under the account holder in the third-party provider. -### Price Selection with Price List +*** -### Code +## Save Payment Methods -```ts -const priceList = pricingModuleService.createPriceLists([{ - title: "Summer Price List", - description: "Price list for summer sale", - starts_at: Date.parse("01/10/2023").toString(), - ends_at: Date.parse("31/10/2023").toString(), - rules: { - region_id: ['PL'] - }, - type: "sale", - prices: [ - { - amount: 400, - currency_code: "EUR", - price_set_id: priceSet.id, - }, - { - amount: 450, - currency_code: "EUR", - price_set_id: priceSet.id, - }, - ], -}]); +If a payment provider supports saving payment methods for a customer, they must implement the following methods: -const price = await pricingModuleService.calculatePrices( - { id: [priceSet.id] }, - { - context: { - currency_code: "EUR", - region_id: "PL", - city: "krakow" - } - } -) -``` +- `createAccountHolder`: Creates an account holder in the payment provider. The Payment Module uses this method before creating the account holder in Medusa, and uses the returned data to set fields like `external_id` and `data` in the created `AccountHolder` record. +- `deleteAccountHolder`: Deletes an account holder in the payment provider. The Payment Module uses this method when an account holder is deleted in Medusa. +- `savePaymentMethod`: Saves a payment method for an account holder in the payment provider. +- `listPaymentMethods`: Lists saved payment methods in the third-party service for an account holder. This is useful when displaying the customer's saved payment methods in the storefront. -### Result +Learn more about implementing these methods in the [Create Payment Provider guide](https://docs.medusajs.com/references/payment/provider/index.html.md). +*** -# Price Rules +## Account Holder in Medusa Payment Flows -In this document, you'll learn about price rules for price sets and price lists. +In the Medusa application, when a payment session is created for a registered customer, the Medusa application uses the Payment Module to create an account holder for the customer. -## Price Rule +Consequently, the Payment Module uses the payment provider to create an account holder in the third-party service, then creates the account holder in Medusa. -You can restrict prices by rules. Each rule of a price is represented by the [PriceRule data model](https://docs.medusajs.com/references/pricing/models/PriceRule/index.html.md). +This flow is only supported if the chosen payment provider has implemented the necessary [save payment methods](#save-payment-methods). -The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price. -For exmaple, you create a price restricted to `10557` zip codes. +# Links between Payment Module and Other Modules -![A diagram showcasing the relation between the PriceRule and Price](https://res.cloudinary.com/dza7lstvk/image/upload/v1709648772/Medusa%20Resources/price-rule-1_vy8bn9.jpg) +This document showcases the module links defined between the Payment Module and other commerce modules. -A price can have multiple price rules. +## Summary -For example, a price can be restricted by a region and a zip code. +The Payment Module has the following links to other modules: -![A diagram showcasing the relation between the PriceRule and Price with multiple rules.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709649296/Medusa%20Resources/price-rule-3_pwpocz.jpg) +- [`Cart` data model of Cart Module \<> `PaymentCollection` data model](#cart-module). +- [`Customer` data model of Customer Module \<> `AccountHolder` data model](#customer-module). +- [`Order` data model of Order Module \<> `PaymentCollection` data model](#order-module). +- [`OrderClaim` data model of Order Module \<> `PaymentCollection` data model](#order-module). +- [`OrderExchange` data model of Order Module \<> `PaymentCollection` data model](#order-module). +- [`Region` data model of Region Module \<> `PaymentProvider` data model](#region-module). *** -## Price List Rules +## Cart Module -Rules applied to a price list are represented by the [PriceListRule data model](https://docs.medusajs.com/references/pricing/models/PriceListRule/index.html.md). +The Cart Module provides cart-related features, but not payment processing. -The `rules_count` property of a `PriceList` indicates how many rules are applied to it. +Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart. -![A diagram showcasing the relation between the PriceSet, PriceList, Price, RuleType, and PriceListRuleValue](https://res.cloudinary.com/dza7lstvk/image/upload/v1709641999/Medusa%20Resources/price-list_zd10yd.jpg) +Learn more about this relation in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-collection#usage-with-the-cart-module/index.html.md). +### Retrieve with Query -# Promotion Actions +To retrieve the cart associated with the payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `cart.*` in `fields`: -In this document, you’ll learn about promotion actions and how they’re computed using the [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). +### query.graph -## computeActions Method +```ts +const { data: paymentCollections } = await query.graph({ + entity: "payment_collection", + fields: [ + "cart.*", + ], +}) -The Promotion Module's main service has a [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md) that returns an array of actions to perform on a cart when one or more promotions are applied. +// paymentCollections.cart +``` -Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` property indicating the type of action. +### useQueryGraphStep -*** +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -## Action Types +// ... -### `addItemAdjustment` Action +const { data: paymentCollections } = useQueryGraphStep({ + entity: "payment_collection", + fields: [ + "cart.*", + ], +}) -The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount. - -This action has the following format: - -```ts -export interface AddItemAdjustmentAction { - action: "addItemAdjustment" - item_id: string - amount: number - code: string - description?: string -} +// paymentCollections.cart ``` -This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module, or `OrderLineItemAdjustment` data model in the Order Module. - -Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddItemAdjustmentAction/index.html.md) for details on the object’s properties. - -### `removeItemAdjustment` Action - -The `removeItemAdjustment` action indicates that an adjustment must be removed from a line item. For example, remove the $5 discount. +### Manage with Link -The `computeActions` method accepts any previous item adjustments in the `items` property of the second parameter. +To manage the payment collection of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -This action has the following format: +### link.create ```ts -export interface RemoveItemAdjustmentAction { - action: "removeItemAdjustment" - adjustment_id: string - description?: string - code: string -} -``` +import { Modules } from "@medusajs/framework/utils" -This action means that a new record should be removed of the `LineItemAdjustment` (or `OrderLineItemAdjustment`) with the specified ID in the `adjustment_id` property. +// ... -Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveItemAdjustmentAction/index.html.md) for details on the object’s properties. +await link.create({ + [Modules.CART]: { + cart_id: "cart_123", + }, + [Modules.PAYMENT]: { + payment_collection_id: "paycol_123", + }, +}) +``` -### `addShippingMethodAdjustment` Action +### createRemoteLinkStep -The `addShippingMethodAdjustment` action indicates that an adjustment must be made on a shipping method. For example, make the shipping method free. +```ts +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -This action has the following format: +// ... -```ts -export interface AddShippingMethodAdjustment { - action: "addShippingMethodAdjustment" - shipping_method_id: string - amount: number - code: string - description?: string -} +createRemoteLinkStep({ + [Modules.CART]: { + cart_id: "cart_123", + }, + [Modules.PAYMENT]: { + payment_collection_id: "paycol_123", + }, +}) ``` -This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module, or `OrderShippingMethodAdjustment` data model in the Order Module. +*** -Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddShippingMethodAdjustment/index.html.md) for details on the object’s properties. +## Customer Module -### `removeShippingMethodAdjustment` Action +Medusa defines a link between the `Customer` and `AccountHolder` data models, allowing payment providers to save payment methods for a customer, if the payment provider supports it. -The `removeShippingMethodAdjustment` action indicates that an adjustment must be removed from a shipping method. For example, remove the free shipping discount. +This link is available starting from Medusa `v2.5.0`. -The `computeActions` method accepts any previous shipping method adjustments in the `shipping_methods` property of the second parameter. +### Retrieve with Query -This action has the following format: +To retrieve the customer associated with an account holder with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `customer.*` in `fields`: + +### query.graph ```ts -export interface RemoveShippingMethodAdjustment { - action: "removeShippingMethodAdjustment" - adjustment_id: string - code: string -} -``` +const { data: accountHolders } = await query.graph({ + entity: "account_holder", + fields: [ + "customer.*", + ], +}) -When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` (or `OrderShippingMethodAdjustment`) with the specified ID in the `adjustment_id` property. +// accountHolders.customer +``` -Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveShippingMethodAdjustment/index.html.md) for details on the object’s properties. +### useQueryGraphStep -### `campaignBudgetExceeded` Action +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -When the `campaignBudgetExceeded` action is returned, the promotions within a campaign can no longer be used as the campaign budget has been exceeded. +// ... -This action has the following format: +const { data: accountHolders } = useQueryGraphStep({ + entity: "account_holder", + fields: [ + "customer.*", + ], +}) -```ts -export interface CampaignBudgetExceededAction { - action: "campaignBudgetExceeded" - code: string -} +// accountHolders.customer ``` -Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.CampaignBudgetExceededAction/index.html.md) for details on the object’s properties. +### Manage with Link +To manage the account holders of a customer, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -# Promotion Concepts +### link.create -In this document, you’ll learn about the main promotion and rule concepts in the Promotion Module. +```ts +import { Modules } from "@medusajs/framework/utils" -## What is a Promotion? +// ... -A promotion, represented by the [Promotion data model](https://docs.medusajs.com/references/promotion/models/Promotion/index.html.md), is a discount that can be applied on cart items, shipping methods, or entire orders. +await link.create({ + [Modules.CUSTOMER]: { + customer_id: "cus_123", + }, + [Modules.PAYMENT]: { + account_holder_id: "acchld_123", + }, +}) +``` -A promotion has two types: +### createRemoteLinkStep -- `standard`: A standard promotion with rules. -- `buyget`: “A buy X get Y” promotion with rules. +```ts +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -|\`standard\`|\`buyget\`| -|---|---| -|A coupon code that gives customers 10% off their entire order.|Buy two shirts and get another for free.| -|A coupon code that gives customers $15 off any shirt in their order.|Buy two shirts and get 10% off the entire order.| -|A discount applied automatically for VIP customers that removes 10% off their shipping method’s amount.|Spend $100 and get free shipping.| +// ... -The Medusa Admin UI may not provide a way to create each of these promotion examples. However, they are supported by the Promotion Module and Medusa's workflows and API routes. +createRemoteLinkStep({ + [Modules.CUSTOMER]: { + customer_id: "cus_123", + }, + [Modules.PAYMENT]: { + account_holder_id: "acchld_123", + }, +}) +``` *** -## PromotionRule - -A promotion can be restricted by a set of rules, each rule is represented by the [PromotionRule data model](https://docs.medusajs.com/references/promotion/models/PromotionRule/index.html.md). - -For example, you can create a promotion that only customers of the `VIP` customer group can use. +## Order Module -![A diagram showcasing the relation between Promotion and PromotionRule](https://res.cloudinary.com/dza7lstvk/image/upload/v1709833196/Medusa%20Resources/promotion-promotion-rule_msbx0w.jpg) +An order's payment details are stored in a payment collection. This also applies for claims and exchanges. -A `PromotionRule`'s `attribute` property indicates the property's name to which this rule is applied. +So, Medusa defines links between the `PaymentCollection` data model and the `Order`, `OrderClaim`, and `OrderExchange` data models. -For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values. +![A diagram showcasing an example of how data models from the Order and Payment modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716554726/Medusa%20Resources/order-payment_ubdwok.jpg) -When testing whether a promotion can be applied to a cart, the rule's `attribute` property and its values are tested on the cart itself. +### Retrieve with Query -For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value. +To retrieve the order of a payment collection with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order.*` in `fields`: -*** +### query.graph -## Flexible Rules +```ts +const { data: paymentCollections } = await query.graph({ + entity: "payment_collection", + fields: [ + "order.*", + ], +}) -The `PromotionRule`'s `operator` property adds more flexibility to the rule’s condition rather than simple equality (`eq`). +// paymentCollections.order +``` -For example, to restrict the promotion to only `VIP` and `B2B` customer groups: +### useQueryGraphStep -- Add a `PromotionRule` record with its `attribute` property set to `customer_group_id` and `operator` property to `in`. -- Add two `PromotionRuleValue` records associated with the rule: one with the value `VIP` and the other `B2B`. +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -![A diagram showcasing the relation between PromotionRule and PromotionRuleValue when a rule has multiple values](https://res.cloudinary.com/dza7lstvk/image/upload/v1709897383/Medusa%20Resources/promotion-promotion-rule-multiple_hctpmt.jpg) +// ... -In this case, a customer’s group must be in the `VIP` and `B2B` set of values to use the promotion. +const { data: paymentCollections } = useQueryGraphStep({ + entity: "payment_collection", + fields: [ + "order.*", + ], +}) +// paymentCollections.order +``` -# Application Method +### Manage with Link -In this document, you'll learn what an application method is. +To manage the payment collections of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -## What is an Application Method? +### link.create -The [ApplicationMethod data model](https://docs.medusajs.com/references/promotion/models/ApplicationMethod/index.html.md) defines how a promotion is applied: +```ts +import { Modules } from "@medusajs/framework/utils" -|Property|Purpose| -|---|---| -|\`type\`|Does the promotion discount a fixed amount or a percentage?| -|\`target\_type\`|Is the promotion applied on a cart item, shipping method, or the entire order?| -|\`allocation\`|Is the discounted amount applied on each item or split between the applicable items?| +// ... -## Target Promotion Rules +await link.create({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.PAYMENT]: { + payment_collection_id: "paycol_123", + }, +}) +``` -When the promotion is applied to a cart item or a shipping method, you can restrict which items/shipping methods the promotion is applied to. +### createRemoteLinkStep -The `ApplicationMethod` data model has a collection of `PromotionRule` records to restrict which items or shipping methods the promotion applies to. The `target_rules` property represents this relation. +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -![A diagram showcasing the target\_rules relation between the ApplicationMethod and PromotionRule data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709898273/Medusa%20Resources/application-method-target-rules_hqaymz.jpg) +// ... -In this example, the promotion is only applied on products in the cart having the SKU `SHIRT`. +createRemoteLinkStep({ + [Modules.ORDER]: { + order_id: "order_123", + }, + [Modules.PAYMENT]: { + payment_collection_id: "paycol_123", + }, +}) +``` *** -## Buy Promotion Rules +## Region Module -When the promotion’s type is `buyget`, you must specify the “buy X” condition. For example, a cart must have two shirts before the promotion can be applied. +You can specify for each region which payment providers are available. The Medusa application defines a link between the `PaymentProvider` and the `Region` data models. -The application method has a collection of `PromotionRule` items to define the “buy X” rule. The `buy_rules` property represents this relation. +![A diagram showcasing an example of how resources from the Payment and Region modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711569520/Medusa%20Resources/payment-region_jyo2dz.jpg) -![A diagram showcasing the buy\_rules relation between the ApplicationMethod and PromotionRule data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709898453/Medusa%20Resources/application-method-buy-rules_djjuhw.jpg) - -In this example, the cart must have two products with the SKU `SHIRT` for the promotion to be applied. - - -# Campaign - -In this document, you'll learn about campaigns. - -## What is a Campaign? - -A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) combines promotions under the same conditions, such as start and end dates. - -![A diagram showcasing the relation between the Campaign and Promotion data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899225/Medusa%20Resources/campagin-promotion_hh3qsi.jpg) - -*** - -## Campaign Limits - -Each campaign has a budget represented by the [CampaignBudget data model](https://docs.medusajs.com/references/promotion/models/CampaignBudget/index.html.md). The budget limits how many times the promotion can be used. - -There are two types of budgets: - -- `spend`: An amount that, when crossed, the promotion becomes unusable. For example, if the amount limit is set to `$100`, and the total amount of usage of this promotion crosses that threshold, the promotion can no longer be applied. -- `usage`: The number of times that a promotion can be used. For example, if the usage limit is set to `10`, the promotion can be used only 10 times by customers. After that, it can no longer be applied. - -![A diagram showcasing the relation between the Campaign and CampaignBudget data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899463/Medusa%20Resources/campagin-budget_rvqlmi.jpg) - - -# Links between Promotion Module and Other Modules - -This document showcases the module links defined between the Promotion Module and other commerce modules. - -## Summary - -The Promotion Module has the following links to other modules: - -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. - -- [`Cart` data model of the Cart Module \<> `Promotion` data model](#cart-module). -- [`LineItemAdjustment` data model of the Cart Module \<> `Promotion` data model](#cart-module). (Read-only). -- [`Order` data model of the Order Module \<> `Promotion` data model](#order-module). - -*** - -## Cart Module - -A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link between the `Cart` and `Promotion` data models. - -![A diagram showcasing an example of how data models from the Cart and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711538015/Medusa%20Resources/cart-promotion_kuh9vm.jpg) - -Medusa also defines a read-only link between the `Promotion` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItemAdjustment` data model. This means you can retrieve the details of the promotion applied on a line item, but you don't manage the links in a pivot table in the database. The promotion of a line item is determined by the `promotion_id` property of the `LineItemAdjustment` data model. +This increases the flexibility of your store. For example, you only show during checkout the payment providers associated with the cart's region. ### Retrieve with Query -To retrieve the carts that a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: - -To retrieve the line item adjustments of a promotion, pass `line_item_adjustments.*` in `fields`. +To retrieve the regions of a payment provider with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `regions.*` in `fields`: ### query.graph ```ts -const { data: promotions } = await query.graph({ - entity: "promotion", +const { data: paymentProviders } = await query.graph({ + entity: "payment_provider", fields: [ - "carts.*", + "regions.*", ], }) -// promotions.carts +// paymentProviders.regions ``` ### useQueryGraphStep @@ -23200,19 +22529,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: promotions } = useQueryGraphStep({ - entity: "promotion", +const { data: paymentProviders } = useQueryGraphStep({ + entity: "payment_provider", fields: [ - "carts.*", + "regions.*", ], }) -// promotions.carts +// paymentProviders.regions ``` ### Manage with Link -To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -23222,11 +22551,11 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ - [Modules.CART]: { - cart_id: "cart_123", + [Modules.REGION]: { + region_id: "reg_123", }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", + [Modules.PAYMENT]: { + payment_provider_id: "pp_stripe_stripe", }, }) ``` @@ -23240,387 +22569,459 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.CART]: { - cart_id: "cart_123", + [Modules.REGION]: { + region_id: "reg_123", }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", + [Modules.PAYMENT]: { + payment_provider_id: "pp_stripe_stripe", }, }) ``` -*** - -## Order Module -An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models. +# Payment Collection -![A diagram showcasing an example of how data models from the Order and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716555015/Medusa%20Resources/order-promotion_dgjzzd.jpg) +In this document, you’ll learn what a payment collection is and how the Medusa application uses it with the Cart Module. -### Retrieve with Query +## What's a Payment Collection? -To retrieve the orders a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: +A payment collection stores payment details related to a resource, such as a cart or an order. It’s represented by the [PaymentCollection data model](https://docs.medusajs.com/references/payment/models/PaymentCollection/index.html.md). -### query.graph +Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including: -```ts -const { data: promotions } = await query.graph({ - entity: "promotion", - fields: [ - "orders.*", - ], -}) +- The [payment sessions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-session/index.html.md) that represents the payment amount to authorize. +- The [payments](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment/index.html.md) that are created when a payment session is authorized. They can be captured and refunded. +- The [payment providers](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/index.html.md) that handle the processing of each payment session, including the authorization, capture, and refund. -// promotions.orders -``` +*** -### useQueryGraphStep +## Multiple Payments -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +The payment collection supports multiple payment sessions and payments. -// ... +You can use this to accept payments in increments or split payments across payment providers. -const { data: promotions } = useQueryGraphStep({ - entity: "promotion", - fields: [ - "orders.*", - ], -}) +![Diagram showcasing how a payment collection can have multiple payment sessions and payments](https://res.cloudinary.com/dza7lstvk/image/upload/v1711554695/Medusa%20Resources/payment-collection-multiple-payments_oi3z3n.jpg) -// promotions.orders -``` +*** -### Manage with Link +## Usage with the Cart Module -To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment. -### link.create +During checkout, the Medusa application links a cart to a payment collection, which will be used for further payment processing. -```ts -import { Modules } from "@medusajs/framework/utils" +It also implements the payment flow during checkout as explained in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-flow/index.html.md). -// ... +![Diagram showcasing the relation between the Payment and Cart modules](https://res.cloudinary.com/dza7lstvk/image/upload/v1711537849/Medusa%20Resources/cart-payment_ixziqm.jpg) -await link.create({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", - }, -}) -``` -### createRemoteLinkStep +# Payment -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +In this document, you’ll learn what a payment is and how it's created, captured, and refunded. -// ... +## What's a Payment? -createRemoteLinkStep({ - [Modules.ORDER]: { - order_id: "order_123", - }, - [Modules.PROMOTION]: { - promotion_id: "promo_123", - }, -}) -``` +When a payment session is authorized, a payment, represented by the [Payment data model](https://docs.medusajs.com/references/payment/models/Payment/index.html.md), is created. This payment can later be captured or refunded. +A payment carries many of the data and relations of a payment session: -# Links between Product Module and Other Modules +- It belongs to the same payment collection. +- It’s associated with the same payment provider, which handles further payment processing. +- It stores the payment session’s `data` property in its `data` property, as it’s still useful for the payment provider’s processing. -This document showcases the module links defined between the Product Module and other commerce modules. +*** -## Summary +## Capture Payments -The Product Module has the following links to other modules: +When a payment is captured, a capture, represented by the [Capture data model](https://docs.medusajs.com/references/payment/models/Capture/index.html.md), is created. It holds details related to the capture, such as the amount, the capture date, and more. -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. +The payment can also be captured incrementally, each time a capture record is created for that amount. -- [`Product` data model \<> `Cart` data model of Cart Module](#cart-module). (Read-only). -- [`Product` data model \<> `ShippingProfile` data model of Fulfillment Module](#fulfillment-module). -- [`ProductVariant` data model \<> `InventoryItem` data model of Inventory Module](#inventory-module). -- [`Product` data model \<> `Order` data model of Order Module](#order-module). (Read-only). -- [`ProductVariant` data model \<> `PriceSet` data model of Pricing Module](#pricing-module). -- [`Product` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module). +![A diagram showcasing how a payment's multiple captures are stored](https://res.cloudinary.com/dza7lstvk/image/upload/v1711565445/Medusa%20Resources/payment-capture_f5fve1.jpg) *** -## Cart Module - -Medusa defines read-only links between: +## Refund Payments -- The `Product` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItem` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `LineItem` data model. -- The `ProductVariant` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItem` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `LineItem` data model. +When a payment is refunded, a refund, represented by the [Refund data model](https://docs.medusajs.com/references/payment/models/Refund/index.html.md), is created. It holds details related to the refund, such as the amount, refund date, and more. -### Retrieve with Query +A payment can be refunded multiple times, and each time a refund record is created. -To retrieve the line items of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `line_items.*` in `fields`: +![A diagram showcasing how a payment's multiple refunds are stored](https://res.cloudinary.com/dza7lstvk/image/upload/v1711565555/Medusa%20Resources/payment-refund_lgfvyy.jpg) -To retrieve the line items of a product, pass `line_items.*` in `fields`. -### query.graph +# Accept Payment Flow -```ts -const { data: variants } = await query.graph({ - entity: "variant", - fields: [ - "line_items.*", - ], -}) +In this document, you’ll learn how to implement an accept-payment flow using workflows or the Payment Module's main service. -// variants.line_items -``` +It's highly recommended to use Medusa's workflows to implement this flow. Use the Payment Module's main service for more complex cases. -### useQueryGraphStep +For a guide on how to implement this flow in the storefront, check out [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/index.html.md). -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +## Flow Overview -// ... +![A diagram showcasing the payment flow's steps](https://res.cloudinary.com/dza7lstvk/image/upload/v1711566781/Medusa%20Resources/payment-flow_jblrvw.jpg) -const { data: variants } = useQueryGraphStep({ - entity: "variant", - fields: [ - "line_items.*", - ], -}) +*** -// variants.line_items -``` +## 1. Create a Payment Collection -*** +A payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection. -## Fulfillment Module +For example: -Medusa defines a link between the `Product` data model and the `ShippingProfile` data model of the Fulfillment Module. Each product must belong to a shipping profile. +### Using Workflow -This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0). +```ts +import { createPaymentCollectionForCartWorkflow } from "@medusajs/medusa/core-flows" -### Retrieve with Query +// ... -To retrieve the shipping profile of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_profile.*` in `fields`: +await createPaymentCollectionForCartWorkflow(req.scope) + .run({ + input: { + cart_id: "cart_123", + }, + }) +``` -### query.graph +### Using Service ```ts -const { data: products } = await query.graph({ - entity: "product", - fields: [ - "shipping_profile.*", - ], -}) - -// products.shipping_profile +const paymentCollection = + await paymentModuleService.createPaymentCollections({ + currency_code: "usd", + amount: 5000, + }) ``` -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... +*** -const { data: products } = useQueryGraphStep({ - entity: "product", - fields: [ - "shipping_profile.*", - ], -}) +## 2. Create Payment Sessions -// products.shipping_profile -``` +The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider. -### Manage with Link +So, after creating the payment collection, create at least one payment session for a provider. -To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +For example: -### link.create +### Using Workflow ```ts -import { Modules } from "@medusajs/framework/utils" +import { createPaymentSessionsWorkflow } from "@medusajs/medusa/core-flows" // ... -await link.create({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, - [Modules.FULFILLMENT]: { - shipping_profile_id: "sp_123", - }, -}) +const { result: paymentSesion } = await createPaymentSessionsWorkflow(req.scope) + .run({ + input: { + payment_collection_id: "paycol_123", + provider_id: "stripe", + }, + }) ``` -### createRemoteLinkStep +### Using Service ```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" - -// ... - -createRemoteLinkStep({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, - [Modules.FULFILLMENT]: { - shipping_profile_id: "sp_123", - }, -}) +const paymentSession = + await paymentModuleService.createPaymentSession( + paymentCollection.id, + { + provider_id: "stripe", + currency_code: "usd", + amount: 5000, + data: { + // any necessary data for the + // payment provider + }, + } + ) ``` *** -## Inventory Module - -The Inventory Module provides inventory-management features for any stock-kept item. - -Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. Each product variant has different inventory details. - -![A diagram showcasing an example of how data models from the Product and Inventory modules are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709652779/Medusa%20Resources/product-inventory_kmjnud.jpg) +## 3. Authorize Payment Session -When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation. +Once the customer chooses a payment session, start the authorization process. This may involve some action performed by the third-party payment provider, such as entering a 3DS code. -Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md). +For example: -### Retrieve with Query +### Using Step -To retrieve the inventory items of a product variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `inventory_items.*` in `fields`: +```ts +import { authorizePaymentSessionStep } from "@medusajs/medusa/core-flows" -### query.graph +// ... -```ts -const { data: variants } = await query.graph({ - entity: "variant", - fields: [ - "inventory_items.*", - ], +authorizePaymentSessionStep({ + id: "payses_123", + context: {}, }) - -// variants.inventory_items ``` -### useQueryGraphStep +### Using Service ```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +const payment = authorizePaymentSessionStep({ + id: "payses_123", + context: {}, +}) +``` -// ... +When the payment authorization is successful, a payment is created and returned. -const { data: variants } = useQueryGraphStep({ - entity: "variant", - fields: [ - "inventory_items.*", - ], -}) +### Handling Additional Action -// variants.inventory_items -``` +If you used the `authorizePaymentSessionStep`, you don't need to implement this logic as it's implemented in the step. -### Manage with Link +If the payment authorization isn’t successful, whether because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error. -To manage the inventory items of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +In that case, you can catch that error and, if the session's `status` property is `requires_more`, handle the additional action, then retry the authorization. -### link.create +For example: ```ts -import { Modules } from "@medusajs/framework/utils" - -// ... +try { + const payment = + await paymentModuleService.authorizePaymentSession( + paymentSession.id, + {} + ) +} catch (e) { + // retrieve the payment session again + const updatedPaymentSession = ( + await paymentModuleService.listPaymentSessions({ + id: [paymentSession.id], + }) + )[0] -await link.create({ - [Modules.PRODUCT]: { - variant_id: "variant_123", - }, - [Modules.INVENTORY]: { - inventory_item_id: "iitem_123", - }, -}) + if (updatedPaymentSession.status === "requires_more") { + // TODO perform required action + // TODO authorize payment again. + } +} ``` -### createRemoteLinkStep +*** -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +## 4. Payment Flow Complete -// ... +The payment flow is complete once the payment session is authorized and the payment is created. -createRemoteLinkStep({ - [Modules.PRODUCT]: { - variant_id: "variant_123", - }, - [Modules.INVENTORY]: { - inventory_item_id: "iitem_123", - }, -}) -``` +You can then: -*** +- Capture the payment either using the [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md) or [capturePayment method](https://docs.medusajs.com/references/payment/capturePayment/index.html.md). +- Refund captured amounts using the [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) or [refundPayment method](https://docs.medusajs.com/references/payment/refundPayment/index.html.md). -## Order Module +Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually. -Medusa defines read-only links between: -- the `Product` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `OrderLineItem` data model. -- the `ProductVariant` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `OrderLineItem` data model. +# Payment Module Options -### Retrieve with Query +In this document, you'll learn about the options of the Payment Module. -To retrieve the order line items of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order_items.*` in `fields`: +## All Module Options -To retrieve a product's order line items, pass `order_items.*` in `fields`. +|Option|Description|Required|Default| +|---|---|---|---|---|---|---| +|\`webhook\_delay\`|A number indicating the delay in milliseconds before processing a webhook event.|No|\`5000\`| +|\`webhook\_retries\`|The number of times to retry the webhook event processing in case of an error.|No|\`3\`| +|\`providers\`|An array of payment providers to install and register. Learn more |No|-| -### query.graph +*** -```ts -const { data: variants } = await query.graph({ - entity: "variant", - fields: [ - "order_items.*", - ], -}) +## providers Option -// variants.order_items -``` +The `providers` option is an array of payment module providers. -### useQueryGraphStep +When the Medusa application starts, these providers are registered and can be used to process payments. -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +For example: + +```ts title="medusa-config.ts" +import { Modules } from "@medusajs/framework/utils" // ... -const { data: variants } = useQueryGraphStep({ - entity: "variant", - fields: [ - "order_items.*", +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/payment", + options: { + providers: [ + { + resolve: "@medusajs/medusa/payment-stripe", + id: "stripe", + options: { + // ... + }, + }, + ], + }, + }, ], }) - -// variants.order_items ``` -*** +The `providers` option is an array of objects that accept the following properties: -## Pricing Module +- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory. +- `id`: A string indicating the provider's unique name or ID. +- `options`: An optional object of the module provider's options. -The Product Module doesn't provide pricing-related features. -Instead, Medusa defines a link between the `ProductVariant` and the `PriceSet` data models. A product variant’s prices are stored belonging to a price set. +# Payment Module Provider -![A diagram showcasing an example of how data models from the Pricing and Product Module are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709651464/Medusa%20Resources/product-pricing_vlxsiq.jpg) +In this document, you’ll learn what a payment module provider is. -So, to add prices for a product variant, create a price set and add the prices to it. +## What's a Payment Module Provider? + +A payment module provider registers a payment provider that handles payment processing in the Medusa application. It integrates third-party payment providers, such as Stripe. + +To authorize a payment amount with a payment provider, a payment session is created and associated with that payment provider. The payment provider is then used to handle the authorization. + +After the payment session is authorized, the payment provider is associated with the resulting payment and handles its payment processing, such as to capture or refund payment. + +### List of Payment Module Providers + +- [Stripe](https://docs.medusajs.com/commerce-modules/payment/payment-provider/stripe/index.html.md) + +*** + +## System Payment Provider + +The Payment Module provides a `system` payment provider that acts as a placeholder payment provider. + +It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method. + +*** + +## How are Payment Providers Created? + +A payment provider is a module whose main service extends the `AbstractPaymentProvider` imported from `@medusajs/framework/utils`. + +Refer to [this guide](https://docs.medusajs.com/references/payment/provider/index.html.md) on how to create a payment provider for the Payment Module. + +*** + +## Configure Payment Providers + +The Payment Module accepts a `providers` option that allows you to register providers in your application. + +Learn more about this option in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/module-options#providers/index.html.md). + +*** + +## PaymentProvider Data Model + +When the Medusa application starts and registers the payment providers, it also creates a record of the `PaymentProvider` data model if none exists. + +This data model is used to reference a payment provider and determine whether it’s installed in the application. + + +# Payment Session + +In this document, you’ll learn what a payment session is. + +## What's a Payment Session? + +A payment session, represented by the [PaymentSession data model](https://docs.medusajs.com/references/payment/models/PaymentSession/index.html.md), is a payment amount to be authorized. It’s associated with a payment provider that handles authorizing it. + +A payment collection can have multiple payment sessions. Using this feature, you can implement payment in installments or payments using multiple providers. + +![Diagram showcasing how every payment session has a different payment provider](https://res.cloudinary.com/dza7lstvk/image/upload/v1711565056/Medusa%20Resources/payment-session-provider_guxzqt.jpg) + +*** + +## data Property + +Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data. + +For example, the customer's ID in Stripe is stored in the `data` property. + +*** + +## Payment Session Status + +The `status` property of a payment session indicates its current status. Its value can be: + +- `pending`: The payment session is awaiting authorization. +- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code. +- `authorized`: The payment session is authorized. +- `error`: An error occurred while authorizing the payment. +- `canceled`: The authorization of the payment session has been canceled. + + +# Webhook Events + +In this document, you’ll learn how the Payment Module supports listening to webhook events. + +## What's a Webhook Event? + +A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status. + +This is useful in many cases such as when a payment is being processed asynchronously or when a request is interrupted and the payment provider is sending details on the process later. + +*** + +## getWebhookActionAndData Method + +The Payment Module’s main service has a [getWebhookActionAndData method](https://docs.medusajs.com/references/payment/getWebhookActionAndData/index.html.md) used to handle incoming webhook events from third-party payment services. The method delegates the handling to the associated payment provider, which returns the event's details. + +Medusa implements a webhook listener route at the `/hooks/payment/[identifier]_[provider]` API route, where: + +- `[identifier]` is the `identifier` static property defined in the payment provider. For example, `stripe`. +- `[provider]` is the ID of the provider. For example, `stripe`. + +For example, when integrating basic Stripe payments with the [Stripe Module Provider](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/payment/payment-provider/stripe/index.html.md), the webhook listener route is `/hooks/payment/stripe_stripe`. If you're integrating Stripe's Bancontact payments, the webhook listener route is `/hooks/payment/stripe-bancontact_stripe`. + +Use that webhook listener in your third-party payment provider's configurations. + +![A diagram showcasing the steps of how the getWebhookActionAndData method words](https://res.cloudinary.com/dza7lstvk/image/upload/v1711567415/Medusa%20Resources/payment-webhook_seaocg.jpg) + +If the event's details indicate that the payment should be authorized, then the [authorizePaymentSession method of the main service](https://docs.medusajs.com/references/payment/authorizePaymentSession/index.html.md) is executed on the specified payment session. + +If the event's details indicate that the payment should be captured, then the [capturePayment method of the main service](https://docs.medusajs.com/references/payment/capturePayment/index.html.md) is executed on the payment of the specified payment session. + +### Actions After Webhook Payment Processing + +After the payment webhook actions are processed and the payment is authorized or captured, the Medusa application completes the cart associated with the payment's collection if it's not completed yet. + + +# Links between Product Module and Other Modules + +This document showcases the module links defined between the Product Module and other commerce modules. + +## Summary + +The Product Module has the following links to other modules: + +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. + +- [`Product` data model \<> `Cart` data model of Cart Module](#cart-module). (Read-only). +- [`Product` data model \<> `ShippingProfile` data model of Fulfillment Module](#fulfillment-module). +- [`ProductVariant` data model \<> `InventoryItem` data model of Inventory Module](#inventory-module). +- [`Product` data model \<> `Order` data model of Order Module](#order-module). (Read-only). +- [`ProductVariant` data model \<> `PriceSet` data model of Pricing Module](#pricing-module). +- [`Product` data model \<> `SalesChannel` data model of Sales Channel Module](#sales-channel-module). + +*** + +## Cart Module + +Medusa defines read-only links between: + +- The `Product` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItem` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `LineItem` data model. +- The `ProductVariant` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItem` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `LineItem` data model. ### Retrieve with Query -To retrieve the price set of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`: +To retrieve the line items of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `line_items.*` in `fields`: + +To retrieve the line items of a product, pass `line_items.*` in `fields`. ### query.graph @@ -23628,11 +23029,11 @@ To retrieve the price set of a variant with [Query](https://docs.medusajs.com/do const { data: variants } = await query.graph({ entity: "variant", fields: [ - "price_set.*", + "line_items.*", ], }) -// variants.price_set +// variants.line_items ``` ### useQueryGraphStep @@ -23645,16 +23046,58 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" const { data: variants } = useQueryGraphStep({ entity: "variant", fields: [ - "price_set.*", + "line_items.*", ], }) -// variants.price_set +// variants.line_items +``` + +*** + +## Fulfillment Module + +Medusa defines a link between the `Product` data model and the `ShippingProfile` data model of the Fulfillment Module. Each product must belong to a shipping profile. + +This link is introduced in [Medusa v2.5.0](https://github.com/medusajs/medusa/releases/tag/v2.5.0). + +### Retrieve with Query + +To retrieve the shipping profile of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `shipping_profile.*` in `fields`: + +### query.graph + +```ts +const { data: products } = await query.graph({ + entity: "product", + fields: [ + "shipping_profile.*", + ], +}) + +// products.shipping_profile +``` + +### useQueryGraphStep + +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +// ... + +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: [ + "shipping_profile.*", + ], +}) + +// products.shipping_profile ``` ### Manage with Link -To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the shipping profile of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -23665,10 +23108,10 @@ import { Modules } from "@medusajs/framework/utils" await link.create({ [Modules.PRODUCT]: { - variant_id: "variant_123", + product_id: "prod_123", }, - [Modules.PRICING]: { - price_set_id: "pset_123", + [Modules.FULFILLMENT]: { + shipping_profile_id: "sp_123", }, }) ``` @@ -23683,39 +23126,43 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" createRemoteLinkStep({ [Modules.PRODUCT]: { - variant_id: "variant_123", + product_id: "prod_123", }, - [Modules.PRICING]: { - price_set_id: "pset_123", + [Modules.FULFILLMENT]: { + shipping_profile_id: "sp_123", }, }) ``` *** -## Sales Channel Module +## Inventory Module -The Sales Channel Module provides functionalities to manage multiple selling channels in your store. +The Inventory Module provides inventory-management features for any stock-kept item. -Medusa defines a link between the `Product` and `SalesChannel` data models. A product can have different availability in different sales channels. +Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. Each product variant has different inventory details. -![A diagram showcasing an example of how data models from the Product and Sales Channel modules are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709651840/Medusa%20Resources/product-sales-channel_t848ik.jpg) +![A diagram showcasing an example of how data models from the Product and Inventory modules are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709652779/Medusa%20Resources/product-inventory_kmjnud.jpg) + +When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation. + +Learn more about product variant's inventory management in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/variant-inventory/index.html.md). ### Retrieve with Query -To retrieve the sales channels of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`: +To retrieve the inventory items of a product variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `inventory_items.*` in `fields`: ### query.graph ```ts -const { data: products } = await query.graph({ - entity: "product", +const { data: variants } = await query.graph({ + entity: "variant", fields: [ - "sales_channels.*", + "inventory_items.*", ], }) -// products.sales_channels +// variants.inventory_items ``` ### useQueryGraphStep @@ -23725,19 +23172,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: products } = useQueryGraphStep({ - entity: "product", +const { data: variants } = useQueryGraphStep({ + entity: "variant", fields: [ - "sales_channels.*", + "inventory_items.*", ], }) -// products.sales_channels +// variants.inventory_items ``` ### Manage with Link -To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the inventory items of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -23748,10 +23195,10 @@ import { Modules } from "@medusajs/framework/utils" await link.create({ [Modules.PRODUCT]: { - product_id: "prod_123", + variant_id: "variant_123", }, - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", + [Modules.INVENTORY]: { + inventory_item_id: "iitem_123", }, }) ``` @@ -23766,113 +23213,86 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" createRemoteLinkStep({ [Modules.PRODUCT]: { - product_id: "prod_123", + variant_id: "variant_123", }, - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", + [Modules.INVENTORY]: { + inventory_item_id: "iitem_123", }, }) ``` +*** -# Product Variant Inventory +## Order Module -# Product Variant Inventory +Medusa defines read-only links between: -In this guide, you'll learn about the inventory management features related to product variants. - -## Configure Inventory Management of Product Variants - -A product variant, represented by the [ProductVariant](https://docs.medusajs.com/references/product/models/ProductVariant/index.html.md) data model, has a `manage_inventory` field that's disabled by default. This field indicates whether you'll manage the inventory quantity of the product variant. - -The Product Module doesn't provide inventory-management features. Instead, the Medusa application uses the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to manage inventory for products and variants. When `manage_inventory` is disabled, the Medusa application always considers the product variant to be in stock. This is useful if your product's variants aren't items that can be stocked, such as digital products, or they don't have a limited stock quantity. - -When `manage_inventory` is enabled, the Medusa application tracks the inventory of the product variant using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md). For example, when a customer purchases a product variant, the Medusa application decrements the stocked quantity of the product variant. - -*** - -## How the Medusa Application Manages Inventory - -When a product variant has `manage_inventory` enabled, the Medusa application creates an inventory item using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) and links it to the product variant. - -![Diagram showcasing the link between a product variant and its inventory item](https://res.cloudinary.com/dza7lstvk/image/upload/v1709652779/Medusa%20Resources/product-inventory_kmjnud.jpg) - -The inventory item has one or more locations, called inventory levels, that represent the stock quantity of the product variant at a specific location. This allows you to manage inventory across multiple warehouses, such as a warehouse in the US and another in Europe. - -![Diagram showcasing the link between a variant and its inventory item, and the inventory item's level.](https://res.cloudinary.com/dza7lstvk/image/upload/v1738580390/Medusa%20Resources/variant-inventory-level_bbee2t.jpg) - -Learn more about inventory concepts in the [Inventory Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md). - -The Medusa application represents and manages stock locations using the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md). It creates a read-only link between the `InventoryLevel` and `StockLocation` data models so that it can retrieve the stock location of an inventory level. +- the `Product` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model. This means you can retrieve the details of a line item's product, but you don't manage the links in a pivot table in the database. The product of a line item is determined by the `product_id` property of the `OrderLineItem` data model. +- the `ProductVariant` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `OrderLineItem` data model. This means you can retrieve the details of a line item's variant, but you don't manage the links in a pivot table in the database. The variant of a line item is determined by the `variant_id` property of the `OrderLineItem` data model. -![Diagram showcasing the read-only link between an inventory level and a stock location](https://res.cloudinary.com/dza7lstvk/image/upload/v1738582163/Medusa%20Resources/inventory-level-stock_amxfg5.jpg) +### Retrieve with Query -Learn more about the Stock Location Module in the [Stock Location Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/concepts/index.html.md). +To retrieve the order line items of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `order_items.*` in `fields`: -### Product Inventory in Storefronts +To retrieve a product's order line items, pass `order_items.*` in `fields`. -When a storefront sends a request to the Medusa application, it must always pass a [publishable API key](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md) in the request header. This API key specifies the sales channels, available through the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md), of the storefront. +### query.graph -The Medusa application links sales channels to stock locations, indicating the locations available for a specific sales channel. So, all inventory-related operations are scoped by the sales channel and its associated stock locations. +```ts +const { data: variants } = await query.graph({ + entity: "variant", + fields: [ + "order_items.*", + ], +}) -For example, the availability of a product variant is determined by the `stocked_quantity` of its inventory level at the stock location linked to the storefront's sales channel. +// variants.order_items +``` -![Diagram showcasing the overall relations between inventory, stock location, and sales channel concepts](https://res.cloudinary.com/dza7lstvk/image/upload/v1738582163/Medusa%20Resources/inventory-stock-sales_fknoxw.jpg) +### useQueryGraphStep -*** +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -## Variant Back Orders +// ... -Product variants have an `allow_backorder` field that's disabled by default. When enabled, the Medusa application allows customers to purchase the product variant even when it's out of stock. Use this when your product variant is available through on-demand or pre-order purchase. +const { data: variants } = useQueryGraphStep({ + entity: "variant", + fields: [ + "order_items.*", + ], +}) -You can also allow customers to subscribe to restock notifications of a product variant as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/recipes/commerce-automation/restock-notification/index.html.md). +// variants.order_items +``` *** -## Additional Resources - -The following guides provide more details on inventory management in the Medusa application: - -- [Inventory Kits in the Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Learn how you can implement bundled or multi-part products through the Inventory Module. -- [Inventory in Flows](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-in-flows/index.html.md): Learn how Medusa utilizes inventory management in different flows. -- [Storefront guide: how to retrieve a product variant's inventory details](https://docs.medusajs.com/resources/storefront-development/products/inventory/index.html.md). - - -# Links between Region Module and Other Modules - -This document showcases the module links defined between the Region Module and other commerce modules. - -## Summary - -The Region Module has the following links to other modules: - -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. +## Pricing Module -- [`Region` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only) -- [`Region` data model \<> `Order` data model of the Order Module](#order-module). (Read-only) -- [`Region` data model \<> `PaymentProvider` data model of the Payment Module](#payment-module). +The Product Module doesn't provide pricing-related features. -*** +Instead, Medusa defines a link between the `ProductVariant` and the `PriceSet` data models. A product variant’s prices are stored belonging to a price set. -## Cart Module +![A diagram showcasing an example of how data models from the Pricing and Product Module are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709651464/Medusa%20Resources/product-pricing_vlxsiq.jpg) -Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's carts, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model. +So, to add prices for a product variant, create a price set and add the prices to it. ### Retrieve with Query -To retrieve the carts of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: +To retrieve the price set of a variant with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `price_set.*` in `fields`: ### query.graph ```ts -const { data: regions } = await query.graph({ - entity: "region", +const { data: variants } = await query.graph({ + entity: "variant", fields: [ - "carts.*", + "price_set.*", ], }) -// regions.carts +// variants.price_set ``` ### useQueryGraphStep @@ -23882,81 +23302,80 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: regions } = useQueryGraphStep({ - entity: "region", +const { data: variants } = useQueryGraphStep({ + entity: "variant", fields: [ - "carts.*", + "price_set.*", ], }) -// regions.carts +// variants.price_set ``` -*** - -## Order Module +### Manage with Link -Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's orders, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model. +To manage the price set of a variant, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -### Retrieve with Query +### link.create -To retrieve the orders of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: +```ts +import { Modules } from "@medusajs/framework/utils" -### query.graph +// ... -```ts -const { data: regions } = await query.graph({ - entity: "region", - fields: [ - "orders.*", - ], +await link.create({ + [Modules.PRODUCT]: { + variant_id: "variant_123", + }, + [Modules.PRICING]: { + price_set_id: "pset_123", + }, }) - -// regions.orders ``` -### useQueryGraphStep +### createRemoteLinkStep ```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... -const { data: regions } = useQueryGraphStep({ - entity: "region", - fields: [ - "orders.*", - ], +createRemoteLinkStep({ + [Modules.PRODUCT]: { + variant_id: "variant_123", + }, + [Modules.PRICING]: { + price_set_id: "pset_123", + }, }) - -// regions.orders ``` *** -## Payment Module +## Sales Channel Module -You can specify for each region which payment providers are available for use. +The Sales Channel Module provides functionalities to manage multiple selling channels in your store. -Medusa defines a module link between the `PaymentProvider` and the `Region` data models. +Medusa defines a link between the `Product` and `SalesChannel` data models. A product can have different availability in different sales channels. -![A diagram showcasing an example of how resources from the Payment and Region modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711569520/Medusa%20Resources/payment-region_jyo2dz.jpg) +![A diagram showcasing an example of how data models from the Product and Sales Channel modules are linked.](https://res.cloudinary.com/dza7lstvk/image/upload/v1709651840/Medusa%20Resources/product-sales-channel_t848ik.jpg) ### Retrieve with Query -To retrieve the payment providers of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_providers.*` in `fields`: +To retrieve the sales channels of a product with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`: ### query.graph ```ts -const { data: regions } = await query.graph({ - entity: "region", +const { data: products } = await query.graph({ + entity: "product", fields: [ - "payment_providers.*", + "sales_channels.*", ], }) -// regions.payment_providers +// products.sales_channels ``` ### useQueryGraphStep @@ -23966,19 +23385,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: regions } = useQueryGraphStep({ - entity: "region", +const { data: products } = useQueryGraphStep({ + entity: "product", fields: [ - "payment_providers.*", + "sales_channels.*", ], }) -// regions.payment_providers +// products.sales_channels ``` ### Manage with Link -To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -23988,11 +23407,11 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ - [Modules.REGION]: { - region_id: "reg_123", + [Modules.PRODUCT]: { + product_id: "prod_123", }, - [Modules.PAYMENT]: { - payment_provider_id: "pp_stripe_stripe", + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", }, }) ``` @@ -24006,371 +23425,347 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.REGION]: { - region_id: "reg_123", + [Modules.PRODUCT]: { + product_id: "prod_123", }, - [Modules.PAYMENT]: { - payment_provider_id: "pp_stripe_stripe", + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", }, }) ``` -# Tax-Inclusive Pricing +# Product Variant Inventory -In this document, you’ll learn about tax-inclusive pricing and how it's used when calculating prices. +# Product Variant Inventory -## What is Tax-Inclusive Pricing? +In this guide, you'll learn about the inventory management features related to product variants. -A tax-inclusive price is a price of a resource that includes taxes. Medusa calculates the tax amount from the price rather than adds the amount to it. +## Configure Inventory Management of Product Variants -For example, if a product’s price is $50, the tax rate is 2%, and tax-inclusive pricing is enabled, then the product's price is $49, and the applied tax amount is $1. +A product variant, represented by the [ProductVariant](https://docs.medusajs.com/references/product/models/ProductVariant/index.html.md) data model, has a `manage_inventory` field that's disabled by default. This field indicates whether you'll manage the inventory quantity of the product variant. -*** +The Product Module doesn't provide inventory-management features. Instead, the Medusa application uses the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) to manage inventory for products and variants. When `manage_inventory` is disabled, the Medusa application always considers the product variant to be in stock. This is useful if your product's variants aren't items that can be stocked, such as digital products, or they don't have a limited stock quantity. -## How is Tax-Inclusive Pricing Set? +When `manage_inventory` is enabled, the Medusa application tracks the inventory of the product variant using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md). For example, when a customer purchases a product variant, the Medusa application decrements the stocked quantity of the product variant. -The [PricePreference data model](https://docs.medusajs.com/references/pricing/PricePreference/index.html.md) holds the tax-inclusive setting for a context. It has two properties that indicate the context: +*** -- `attribute`: The name of the attribute to compare against. For example, `region_id` or `currency_code`. -- `value`: The attribute’s value. For example, `reg_123` or `usd`. +## How the Medusa Application Manages Inventory -Only `region_id` and `currency_code` are supported as an `attribute` at the moment. +When a product variant has `manage_inventory` enabled, the Medusa application creates an inventory item using the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md) and links it to the product variant. -The `is_tax_inclusive` property indicates whether tax-inclusivity is enabled in the specified context. +![Diagram showcasing the link between a product variant and its inventory item](https://res.cloudinary.com/dza7lstvk/image/upload/v1709652779/Medusa%20Resources/product-inventory_kmjnud.jpg) -For example: +The inventory item has one or more locations, called inventory levels, that represent the stock quantity of the product variant at a specific location. This allows you to manage inventory across multiple warehouses, such as a warehouse in the US and another in Europe. -```json -{ - "attribute": "currency_code", - "value": "USD", - "is_tax_inclusive": true, -} -``` +![Diagram showcasing the link between a variant and its inventory item, and the inventory item's level.](https://res.cloudinary.com/dza7lstvk/image/upload/v1738580390/Medusa%20Resources/variant-inventory-level_bbee2t.jpg) -In this example, tax-inclusivity is enabled for the `USD` currency code. +Learn more about inventory concepts in the [Inventory Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/concepts/index.html.md). -*** +The Medusa application represents and manages stock locations using the [Stock Location Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/index.html.md). It creates a read-only link between the `InventoryLevel` and `StockLocation` data models so that it can retrieve the stock location of an inventory level. -## Tax-Inclusive Pricing in Price Calculation +![Diagram showcasing the read-only link between an inventory level and a stock location](https://res.cloudinary.com/dza7lstvk/image/upload/v1738582163/Medusa%20Resources/inventory-level-stock_amxfg5.jpg) -### Tax Context +Learn more about the Stock Location Module in the [Stock Location Module's documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/stock-location/concepts/index.html.md). -As mentioned in the [Price Calculation documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), The `calculatePrices` method accepts as a parameter a calculation context. +### Product Inventory in Storefronts -To get accurate tax results, pass the `region_id` and / or `currency_code` in the calculation context. +When a storefront sends a request to the Medusa application, it must always pass a [publishable API key](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/publishable-api-keys/index.html.md) in the request header. This API key specifies the sales channels, available through the [Sales Channel Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/sales-channel/index.html.md), of the storefront. -### Returned Tax Properties +The Medusa application links sales channels to stock locations, indicating the locations available for a specific sales channel. So, all inventory-related operations are scoped by the sales channel and its associated stock locations. -The `calculatePrices` method returns two properties related to tax-inclusivity: +For example, the availability of a product variant is determined by the `stocked_quantity` of its inventory level at the stock location linked to the storefront's sales channel. -Learn more about the returned properties in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md). +![Diagram showcasing the overall relations between inventory, stock location, and sales channel concepts](https://res.cloudinary.com/dza7lstvk/image/upload/v1738582163/Medusa%20Resources/inventory-stock-sales_fknoxw.jpg) -- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive. -- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive. +*** -A price is considered tax-inclusive if: - -1. It belongs to the region or currency code specified in the calculation context; -2. and the region or currency code has a price preference with `is_tax_inclusive` enabled. - -### Tax Context Precedence +## Variant Back Orders -A region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive if: +Product variants have an `allow_backorder` field that's disabled by default. When enabled, the Medusa application allows customers to purchase the product variant even when it's out of stock. Use this when your product variant is available through on-demand or pre-order purchase. -- both the `region_id` and `currency_code` are provided in the calculation context; -- the selected price belongs to the region; -- and the region has a price preference +You can also allow customers to subscribe to restock notifications of a product variant as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/recipes/commerce-automation/restock-notification/index.html.md). +*** -# Links between Sales Channel Module and Other Modules +## Additional Resources -This document showcases the module links defined between the Sales Channel Module and other commerce modules. +The following guides provide more details on inventory management in the Medusa application: -## Summary +- [Inventory Kits in the Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-kit/index.html.md): Learn how you can implement bundled or multi-part products through the Inventory Module. +- [Inventory in Flows](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/inventory-in-flows/index.html.md): Learn how Medusa utilizes inventory management in different flows. +- [Storefront guide: how to retrieve a product variant's inventory details](https://docs.medusajs.com/resources/storefront-development/products/inventory/index.html.md). -The Sales Channel Module has the following links to other modules: -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. +# Promotion Actions -- [`ApiKey` data model of the API Key Module \<> `SalesChannel` data model](#api-key-module). -- [`SalesChannel` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only) -- [`SalesChannel` data model \<> `Order` data model of the Order Module](#order-module). (Read-only) -- [`Product` data model of the Product Module \<> `SalesChannel` data model](#product-module). -- [`SalesChannel` data model \<> `StockLocation` data model of the Stock Location Module](#stock-location-module). +In this document, you’ll learn about promotion actions and how they’re computed using the [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md). -*** +## computeActions Method -## API Key Module +The Promotion Module's main service has a [computeActions method](https://docs.medusajs.com/references/promotion/computeActions/index.html.md) that returns an array of actions to perform on a cart when one or more promotions are applied. -A publishable API key allows you to easily specify the sales channel scope in a client request. +Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` property indicating the type of action. -Medusa defines a link between the `ApiKey` and the `SalesChannel` data models. +*** -![A diagram showcasing an example of how resources from the Sales Channel and API Key modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1709812064/Medusa%20Resources/sales-channel-api-key_zmqi2l.jpg) +## Action Types -### Retrieve with Query +### `addItemAdjustment` Action -To retrieve the API keys associated with a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `publishable_api_keys.*` in `fields`: +The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount. -### query.graph +This action has the following format: ```ts -const { data: salesChannels } = await query.graph({ - entity: "sales_channel", - fields: [ - "publishable_api_keys.*", - ], -}) - -// salesChannels.publishable_api_keys +export interface AddItemAdjustmentAction { + action: "addItemAdjustment" + item_id: string + amount: number + code: string + description?: string +} ``` -### useQueryGraphStep - -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" - -// ... +This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module, or `OrderLineItemAdjustment` data model in the Order Module. -const { data: salesChannels } = useQueryGraphStep({ - entity: "sales_channel", - fields: [ - "publishable_api_keys.*", - ], -}) +Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddItemAdjustmentAction/index.html.md) for details on the object’s properties. -// salesChannels.publishable_api_keys -``` +### `removeItemAdjustment` Action -### Manage with Link +The `removeItemAdjustment` action indicates that an adjustment must be removed from a line item. For example, remove the $5 discount. -To manage the sales channels of an API key, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +The `computeActions` method accepts any previous item adjustments in the `items` property of the second parameter. -### link.create +This action has the following format: ```ts -import { Modules } from "@medusajs/framework/utils" +export interface RemoveItemAdjustmentAction { + action: "removeItemAdjustment" + adjustment_id: string + description?: string + code: string +} +``` -// ... +This action means that a new record should be removed of the `LineItemAdjustment` (or `OrderLineItemAdjustment`) with the specified ID in the `adjustment_id` property. -await link.create({ - [Modules.API_KEY]: { - api_key_id: "apk_123", - }, - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", - }, -}) -``` +Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveItemAdjustmentAction/index.html.md) for details on the object’s properties. -### createRemoteLinkStep +### `addShippingMethodAdjustment` Action -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +The `addShippingMethodAdjustment` action indicates that an adjustment must be made on a shipping method. For example, make the shipping method free. -// ... +This action has the following format: -createRemoteLinkStep({ - [Modules.API_KEY]: { - api_key_id: "apk_123", - }, - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", - }, -}) +```ts +export interface AddShippingMethodAdjustment { + action: "addShippingMethodAdjustment" + shipping_method_id: string + amount: number + code: string + description?: string +} ``` -*** +This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module, or `OrderShippingMethodAdjustment` data model in the Order Module. -## Cart Module +Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.AddShippingMethodAdjustment/index.html.md) for details on the object’s properties. -Medusa defines a read-only link between the `SalesChannel` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a sales channel's carts, but you don't manage the links in a pivot table in the database. The sales channel of a cart is determined by the `sales_channel_id` property of the `Cart` data model. +### `removeShippingMethodAdjustment` Action -### Retrieve with Query +The `removeShippingMethodAdjustment` action indicates that an adjustment must be removed from a shipping method. For example, remove the free shipping discount. -To retrieve the carts of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: +The `computeActions` method accepts any previous shipping method adjustments in the `shipping_methods` property of the second parameter. -### query.graph +This action has the following format: ```ts -const { data: salesChannels } = await query.graph({ - entity: "sales_channel", - fields: [ - "carts.*", - ], -}) - -// salesChannels.carts +export interface RemoveShippingMethodAdjustment { + action: "removeShippingMethodAdjustment" + adjustment_id: string + code: string +} ``` -### useQueryGraphStep +When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` (or `OrderShippingMethodAdjustment`) with the specified ID in the `adjustment_id` property. -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.RemoveShippingMethodAdjustment/index.html.md) for details on the object’s properties. -// ... +### `campaignBudgetExceeded` Action -const { data: salesChannels } = useQueryGraphStep({ - entity: "sales_channel", - fields: [ - "carts.*", - ], -}) +When the `campaignBudgetExceeded` action is returned, the promotions within a campaign can no longer be used as the campaign budget has been exceeded. -// salesChannels.carts -``` +This action has the following format: -*** +```ts +export interface CampaignBudgetExceededAction { + action: "campaignBudgetExceeded" + code: string +} +``` -## Order Module +Refer to [this reference](https://docs.medusajs.com/references/promotion/interfaces/promotion.CampaignBudgetExceededAction/index.html.md) for details on the object’s properties. -Medusa defines a read-only link between the `SalesChannel` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model. This means you can retrieve the details of a sales channel's orders, but you don't manage the links in a pivot table in the database. The sales channel of an order is determined by the `sales_channel_id` property of the `Order` data model. -### Retrieve with Query +# Application Method -To retrieve the orders of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: +In this document, you'll learn what an application method is. -### query.graph +## What is an Application Method? -```ts -const { data: salesChannels } = await query.graph({ - entity: "sales_channel", - fields: [ - "orders.*", - ], -}) +The [ApplicationMethod data model](https://docs.medusajs.com/references/promotion/models/ApplicationMethod/index.html.md) defines how a promotion is applied: -// salesChannels.orders -``` +|Property|Purpose| +|---|---| +|\`type\`|Does the promotion discount a fixed amount or a percentage?| +|\`target\_type\`|Is the promotion applied on a cart item, shipping method, or the entire order?| +|\`allocation\`|Is the discounted amount applied on each item or split between the applicable items?| -### useQueryGraphStep +## Target Promotion Rules -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +When the promotion is applied to a cart item or a shipping method, you can restrict which items/shipping methods the promotion is applied to. -// ... +The `ApplicationMethod` data model has a collection of `PromotionRule` records to restrict which items or shipping methods the promotion applies to. The `target_rules` property represents this relation. -const { data: salesChannels } = useQueryGraphStep({ - entity: "sales_channel", - fields: [ - "orders.*", - ], -}) +![A diagram showcasing the target\_rules relation between the ApplicationMethod and PromotionRule data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709898273/Medusa%20Resources/application-method-target-rules_hqaymz.jpg) -// salesChannels.orders -``` +In this example, the promotion is only applied on products in the cart having the SKU `SHIRT`. *** -## Product Module +## Buy Promotion Rules -A product has different availability for different sales channels. Medusa defines a link between the `Product` and the `SalesChannel` data models. +When the promotion’s type is `buyget`, you must specify the “buy X” condition. For example, a cart must have two shirts before the promotion can be applied. -![A diagram showcasing an example of how resources from the Sales Channel and Product modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1709809833/Medusa%20Resources/product-sales-channel_t848ik.jpg) +The application method has a collection of `PromotionRule` items to define the “buy X” rule. The `buy_rules` property represents this relation. -A product can be available in more than one sales channel. You can retrieve only the products of a sales channel. +![A diagram showcasing the buy\_rules relation between the ApplicationMethod and PromotionRule data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709898453/Medusa%20Resources/application-method-buy-rules_djjuhw.jpg) -### Retrieve with Query +In this example, the cart must have two products with the SKU `SHIRT` for the promotion to be applied. -To retrieve the products of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`: -### query.graph +# Campaign -```ts -const { data: salesChannels } = await query.graph({ - entity: "sales_channel", - fields: [ - "products.*", - ], -}) +In this document, you'll learn about campaigns. -// salesChannels.products -``` +## What is a Campaign? -### useQueryGraphStep +A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) combines promotions under the same conditions, such as start and end dates. -```ts -import { useQueryGraphStep } from "@medusajs/medusa/core-flows" +![A diagram showcasing the relation between the Campaign and Promotion data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899225/Medusa%20Resources/campagin-promotion_hh3qsi.jpg) -// ... +*** -const { data: salesChannels } = useQueryGraphStep({ - entity: "sales_channel", - fields: [ - "products.*", - ], -}) +## Campaign Limits -// salesChannels.products -``` +Each campaign has a budget represented by the [CampaignBudget data model](https://docs.medusajs.com/references/promotion/models/CampaignBudget/index.html.md). The budget limits how many times the promotion can be used. -### Manage with Link +There are two types of budgets: -To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +- `spend`: An amount that, when crossed, the promotion becomes unusable. For example, if the amount limit is set to `$100`, and the total amount of usage of this promotion crosses that threshold, the promotion can no longer be applied. +- `usage`: The number of times that a promotion can be used. For example, if the usage limit is set to `10`, the promotion can be used only 10 times by customers. After that, it can no longer be applied. -### link.create +![A diagram showcasing the relation between the Campaign and CampaignBudget data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899463/Medusa%20Resources/campagin-budget_rvqlmi.jpg) -```ts -import { Modules } from "@medusajs/framework/utils" -// ... +# Promotion Concepts -await link.create({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", - }, -}) -``` +In this document, you’ll learn about the main promotion and rule concepts in the Promotion Module. -### createRemoteLinkStep +## What is a Promotion? -```ts -import { Modules } from "@medusajs/framework/utils" -import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" +A promotion, represented by the [Promotion data model](https://docs.medusajs.com/references/promotion/models/Promotion/index.html.md), is a discount that can be applied on cart items, shipping methods, or entire orders. -// ... +A promotion has two types: -createRemoteLinkStep({ - [Modules.PRODUCT]: { - product_id: "prod_123", - }, - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", - }, -}) -``` +- `standard`: A standard promotion with rules. +- `buyget`: “A buy X get Y” promotion with rules. + +|\`standard\`|\`buyget\`| +|---|---| +|A coupon code that gives customers 10% off their entire order.|Buy two shirts and get another for free.| +|A coupon code that gives customers $15 off any shirt in their order.|Buy two shirts and get 10% off the entire order.| +|A discount applied automatically for VIP customers that removes 10% off their shipping method’s amount.|Spend $100 and get free shipping.| + +The Medusa Admin UI may not provide a way to create each of these promotion examples. However, they are supported by the Promotion Module and Medusa's workflows and API routes. *** -## Stock Location Module +## PromotionRule -A stock location is associated with a sales channel. This scopes inventory quantities associated with that stock location by the associated sales channel. +A promotion can be restricted by a set of rules, each rule is represented by the [PromotionRule data model](https://docs.medusajs.com/references/promotion/models/PromotionRule/index.html.md). -Medusa defines a link between the `SalesChannel` and `StockLocation` data models. +For example, you can create a promotion that only customers of the `VIP` customer group can use. -![A diagram showcasing an example of how resources from the Sales Channel and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716796872/Medusa%20Resources/sales-channel-location_cqrih1.jpg) +![A diagram showcasing the relation between Promotion and PromotionRule](https://res.cloudinary.com/dza7lstvk/image/upload/v1709833196/Medusa%20Resources/promotion-promotion-rule_msbx0w.jpg) + +A `PromotionRule`'s `attribute` property indicates the property's name to which this rule is applied. + +For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values. + +When testing whether a promotion can be applied to a cart, the rule's `attribute` property and its values are tested on the cart itself. + +For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value. + +*** + +## Flexible Rules + +The `PromotionRule`'s `operator` property adds more flexibility to the rule’s condition rather than simple equality (`eq`). + +For example, to restrict the promotion to only `VIP` and `B2B` customer groups: + +- Add a `PromotionRule` record with its `attribute` property set to `customer_group_id` and `operator` property to `in`. +- Add two `PromotionRuleValue` records associated with the rule: one with the value `VIP` and the other `B2B`. + +![A diagram showcasing the relation between PromotionRule and PromotionRuleValue when a rule has multiple values](https://res.cloudinary.com/dza7lstvk/image/upload/v1709897383/Medusa%20Resources/promotion-promotion-rule-multiple_hctpmt.jpg) + +In this case, a customer’s group must be in the `VIP` and `B2B` set of values to use the promotion. + + +# Links between Promotion Module and Other Modules + +This document showcases the module links defined between the Promotion Module and other commerce modules. + +## Summary + +The Promotion Module has the following links to other modules: + +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. + +- [`Cart` data model of the Cart Module \<> `Promotion` data model](#cart-module). +- [`LineItemAdjustment` data model of the Cart Module \<> `Promotion` data model](#cart-module). (Read-only). +- [`Order` data model of the Order Module \<> `Promotion` data model](#order-module). + +*** + +## Cart Module + +A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link between the `Cart` and `Promotion` data models. + +![A diagram showcasing an example of how data models from the Cart and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711538015/Medusa%20Resources/cart-promotion_kuh9vm.jpg) + +Medusa also defines a read-only link between the `Promotion` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `LineItemAdjustment` data model. This means you can retrieve the details of the promotion applied on a line item, but you don't manage the links in a pivot table in the database. The promotion of a line item is determined by the `promotion_id` property of the `LineItemAdjustment` data model. ### Retrieve with Query -To retrieve the stock locations of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`: +To retrieve the carts that a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: + +To retrieve the line item adjustments of a promotion, pass `line_item_adjustments.*` in `fields`. ### query.graph ```ts -const { data: salesChannels } = await query.graph({ - entity: "sales_channel", +const { data: promotions } = await query.graph({ + entity: "promotion", fields: [ - "stock_locations.*", + "carts.*", ], }) -// salesChannels.stock_locations +// promotions.carts ``` ### useQueryGraphStep @@ -24380,19 +23775,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: salesChannels } = useQueryGraphStep({ - entity: "sales_channel", +const { data: promotions } = useQueryGraphStep({ + entity: "promotion", fields: [ - "stock_locations.*", + "carts.*", ], }) -// salesChannels.stock_locations +// promotions.carts ``` ### Manage with Link -To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the promotions of a cart, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -24402,11 +23797,11 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", + [Modules.CART]: { + cart_id: "cart_123", }, - [Modules.STOCK_LOCATION]: { - sales_channel_id: "sloc_123", + [Modules.PROMOTION]: { + promotion_id: "promo_123", }, }) ``` @@ -24420,103 +23815,38 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", + [Modules.CART]: { + cart_id: "cart_123", }, - [Modules.STOCK_LOCATION]: { - sales_channel_id: "sloc_123", + [Modules.PROMOTION]: { + promotion_id: "promo_123", }, }) ``` - -# Publishable API Keys with Sales Channels - -In this document, you’ll learn what publishable API keys are and how to use them with sales channels. - -## Publishable API Keys with Sales Channels - -A publishable API key, provided by the API Key Module, is a client key scoped to one or more sales channels. - -When sending a request to a Store API route, you must pass a publishable API key in the header of the request: - -```bash -curl http://localhost:9000/store/products \ - x-publishable-api-key: {your_publishable_api_key} -``` - -The Medusa application infers the associated sales channels and ensures that only data relevant to the sales channel are used. - -*** - -## How to Create a Publishable API Key? - -To create a publishable API key, either use the Medusa Admin or the [Admin API Routes](https://docs.medusajs.com/api/admin#publishable-api-keys). - - -# Stock Location Concepts - -In this document, you’ll learn about the main concepts in the Stock Location Module. - -## Stock Location - -A stock location, represented by the `StockLocation` data model, represents a location where stock items are kept. For example, a warehouse. - -Medusa uses stock locations to provide inventory details, from the Inventory Module, per location. - -*** - -## StockLocationAddress - -The `StockLocationAddress` data model belongs to the `StockLocation` data model. It provides more detailed information of the location, such as country code or street address. - - -# Links between Stock Location Module and Other Modules - -This document showcases the module links defined between the Stock Location Module and other commerce modules. - -## Summary - -The Stock Location Module has the following links to other modules: - -Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. - -- [`FulfillmentSet` data model of the Fulfillment Module \<> `StockLocation` data model](#fulfillment-module). -- [`FulfillmentProvider` data model of the Fulfillment Module \<> `StockLocation` data model](#fulfillment-module). -- [`StockLocation` data model \<> `Inventory` data model of the Inventory Module](#inventory-module). -- [`SalesChannel` data model of the Sales Channel Module \<> `StockLocation` data model](#sales-channel-module). - *** -## Fulfillment Module - -A fulfillment set can be conditioned to a specific stock location. - -Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models. - -![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1712567101/Medusa%20Resources/fulfillment-stock-location_nlkf7e.jpg) +## Order Module -Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location. +An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models. -![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399492/Medusa%20Resources/fulfillment-provider-stock-location_b0mulo.jpg) +![A diagram showcasing an example of how data models from the Order and Promotion modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716555015/Medusa%20Resources/order-promotion_dgjzzd.jpg) ### Retrieve with Query -To retrieve the fulfillment sets of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillment_sets.*` in `fields`: - -To retrieve the fulfillment providers, pass `fulfillment_providers.*` in `fields`. +To retrieve the orders a promotion is applied on with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: ### query.graph ```ts -const { data: stockLocations } = await query.graph({ - entity: "stock_location", +const { data: promotions } = await query.graph({ + entity: "promotion", fields: [ - "fulfillment_sets.*", + "orders.*", ], }) -// stockLocations.fulfillment_sets +// promotions.orders ``` ### useQueryGraphStep @@ -24526,19 +23856,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: stockLocations } = useQueryGraphStep({ - entity: "stock_location", +const { data: promotions } = useQueryGraphStep({ + entity: "promotion", fields: [ - "fulfillment_sets.*", + "orders.*", ], }) -// stockLocations.fulfillment_sets +// promotions.orders ``` ### Manage with Link -To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the promotion of an order, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -24548,11 +23878,11 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ - [Modules.STOCK_LOCATION]: { - stock_location_id: "sloc_123", + [Modules.ORDER]: { + order_id: "order_123", }, - [Modules.FULFILLMENT]: { - fulfillment_set_id: "fset_123", + [Modules.PROMOTION]: { + promotion_id: "promo_123", }, }) ``` @@ -24566,36 +23896,51 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.STOCK_LOCATION]: { - stock_location_id: "sloc_123", + [Modules.ORDER]: { + order_id: "order_123", }, - [Modules.FULFILLMENT]: { - fulfillment_set_id: "fset_123", + [Modules.PROMOTION]: { + promotion_id: "promo_123", }, }) ``` -*** -## Inventory Module +# Links between Region Module and Other Modules -Medusa defines a read-only link between the `StockLocation` data model and the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md)'s `InventoryLevel` data model. This means you can retrieve the details of a stock location's inventory levels, but you don't manage the links in a pivot table in the database. The stock location of an inventory level is determined by the `location_id` property of the `InventoryLevel` data model. +This document showcases the module links defined between the Region Module and other commerce modules. + +## Summary + +The Region Module has the following links to other modules: + +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. + +- [`Region` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only) +- [`Region` data model \<> `Order` data model of the Order Module](#order-module). (Read-only) +- [`Region` data model \<> `PaymentProvider` data model of the Payment Module](#payment-module). + +*** + +## Cart Module + +Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's carts, but you don't manage the links in a pivot table in the database. The region of a cart is determined by the `region_id` property of the `Cart` data model. ### Retrieve with Query -To retrieve the inventory levels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `inventory_levels.*` in `fields`: +To retrieve the carts of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: ### query.graph ```ts -const { data: stockLocations } = await query.graph({ - entity: "stock_location", +const { data: regions } = await query.graph({ + entity: "region", fields: [ - "inventory_levels.*", + "carts.*", ], }) -// stockLocations.inventory_levels +// regions.carts ``` ### useQueryGraphStep @@ -24605,41 +23950,81 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: stockLocations } = useQueryGraphStep({ - entity: "stock_location", +const { data: regions } = useQueryGraphStep({ + entity: "region", fields: [ - "inventory_levels.*", + "carts.*", ], }) -// stockLocations.inventory_levels +// regions.carts ``` *** -## Sales Channel Module +## Order Module -A stock location is associated with a sales channel. This scopes inventory quantities in a stock location by the associated sales channel. +Medusa defines a read-only link between the `Region` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a region's orders, but you don't manage the links in a pivot table in the database. The region of an order is determined by the `region_id` property of the `Order` data model. -Medusa defines a link between the `SalesChannel` and `StockLocation` data models. +### Retrieve with Query -![A diagram showcasing an example of how resources from the Sales Channel and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716796872/Medusa%20Resources/sales-channel-location_cqrih1.jpg) +To retrieve the orders of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: + +### query.graph + +```ts +const { data: regions } = await query.graph({ + entity: "region", + fields: [ + "orders.*", + ], +}) + +// regions.orders +``` + +### useQueryGraphStep + +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +// ... + +const { data: regions } = useQueryGraphStep({ + entity: "region", + fields: [ + "orders.*", + ], +}) + +// regions.orders +``` + +*** + +## Payment Module + +You can specify for each region which payment providers are available for use. + +Medusa defines a module link between the `PaymentProvider` and the `Region` data models. + +![A diagram showcasing an example of how resources from the Payment and Region modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1711569520/Medusa%20Resources/payment-region_jyo2dz.jpg) ### Retrieve with Query -To retrieve the sales channels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`: +To retrieve the payment providers of a region with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `payment_providers.*` in `fields`: ### query.graph ```ts -const { data: stockLocations } = await query.graph({ - entity: "stock_location", +const { data: regions } = await query.graph({ + entity: "region", fields: [ - "sales_channels.*", + "payment_providers.*", ], }) -// stockLocations.sales_channels +// regions.payment_providers ``` ### useQueryGraphStep @@ -24649,19 +24034,19 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: stockLocations } = useQueryGraphStep({ - entity: "stock_location", +const { data: regions } = useQueryGraphStep({ + entity: "region", fields: [ - "sales_channels.*", + "payment_providers.*", ], }) -// stockLocations.sales_channels +// regions.payment_providers ``` ### Manage with Link -To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +To manage the payment providers in a region, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): ### link.create @@ -24671,11 +24056,11 @@ import { Modules } from "@medusajs/framework/utils" // ... await link.create({ - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", + [Modules.REGION]: { + region_id: "reg_123", }, - [Modules.STOCK_LOCATION]: { - sales_channel_id: "sloc_123", + [Modules.PAYMENT]: { + payment_provider_id: "pp_stripe_stripe", }, }) ``` @@ -24689,51 +24074,57 @@ import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" // ... createRemoteLinkStep({ - [Modules.SALES_CHANNEL]: { - sales_channel_id: "sc_123", + [Modules.REGION]: { + region_id: "reg_123", }, - [Modules.STOCK_LOCATION]: { - sales_channel_id: "sloc_123", + [Modules.PAYMENT]: { + payment_provider_id: "pp_stripe_stripe", }, }) ``` -# Links between Store Module and Other Modules +# Links between Sales Channel Module and Other Modules -This document showcases the module links defined between the Store Module and other commerce modules. +This document showcases the module links defined between the Sales Channel Module and other commerce modules. ## Summary -The Store Module has the following links to other modules: +The Sales Channel Module has the following links to other modules: Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. -- [`Currency` data model \<> `Currency` data model of Currency Module](#currency-module). (Read-only). +- [`ApiKey` data model of the API Key Module \<> `SalesChannel` data model](#api-key-module). +- [`SalesChannel` data model \<> `Cart` data model of the Cart Module](#cart-module). (Read-only) +- [`SalesChannel` data model \<> `Order` data model of the Order Module](#order-module). (Read-only) +- [`Product` data model of the Product Module \<> `SalesChannel` data model](#product-module). +- [`SalesChannel` data model \<> `StockLocation` data model of the Stock Location Module](#stock-location-module). *** -## Currency Module +## API Key Module -The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol. +A publishable API key allows you to easily specify the sales channel scope in a client request. -Instead, Medusa defines a read-only link between the [Currency Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/index.html.md)'s `Currency` data model and the Store Module's `Currency` data model. This means you can retrieve the details of a store's supported currencies, but you don't manage the links in a pivot table in the database. The currencies of a store are determined by the `currency_code` of the `Currency` data model in the Store Module. +Medusa defines a link between the `ApiKey` and the `SalesChannel` data models. + +![A diagram showcasing an example of how resources from the Sales Channel and API Key modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1709812064/Medusa%20Resources/sales-channel-api-key_zmqi2l.jpg) ### Retrieve with Query -To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`: +To retrieve the API keys associated with a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `publishable_api_keys.*` in `fields`: ### query.graph ```ts -const { data: stores } = await query.graph({ - entity: "store", +const { data: salesChannels } = await query.graph({ + entity: "sales_channel", fields: [ - "supported_currencies.currency.*", + "publishable_api_keys.*", ], }) -// stores.supported_currencies +// salesChannels.publishable_api_keys ``` ### useQueryGraphStep @@ -24743,1372 +24134,1986 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -const { data: stores } = useQueryGraphStep({ - entity: "store", +const { data: salesChannels } = useQueryGraphStep({ + entity: "sales_channel", fields: [ - "supported_currencies.currency.*", + "publishable_api_keys.*", ], }) -// stores.supported_currencies +// salesChannels.publishable_api_keys ``` +### Manage with Link -# User Module Options - -In this document, you'll learn about the options of the User Module. +To manage the sales channels of an API key, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -## Module Options +### link.create -```ts title="medusa-config.ts" +```ts import { Modules } from "@medusajs/framework/utils" // ... -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/user", - options: { - jwt_secret: process.env.JWT_SECRET, - }, - }, - ], +await link.create({ + [Modules.API_KEY]: { + api_key_id: "apk_123", + }, + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", + }, }) ``` -|Option|Description|Required| -|---|---|---|---|---| -|\`jwt\_secret\`|A string indicating the secret used to sign the invite tokens.|Yes| +### createRemoteLinkStep -### Environment Variables +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -Make sure to add the necessary environment variables for the above options in `.env`: +// ... -```bash -JWT_SECRET=supersecret +createRemoteLinkStep({ + [Modules.API_KEY]: { + api_key_id: "apk_123", + }, + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", + }, +}) ``` +*** -# User Creation Flows - -In this document, learn the different ways to create a user using the User Module. - -## Straightforward User Creation - -To create a user, use the [create method of the User Module’s main service](https://docs.medusajs.com/references/user/create/index.html.md): - -```ts -const user = await userModuleService.createUsers({ - email: "user@example.com", -}) -``` +## Cart Module -You can pair this with the Auth Module to allow the user to authenticate, as explained in a [later section](#create-identity-with-the-auth-module). +Medusa defines a read-only link between the `SalesChannel` data model and the [Cart Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/cart/index.html.md)'s `Cart` data model. This means you can retrieve the details of a sales channel's carts, but you don't manage the links in a pivot table in the database. The sales channel of a cart is determined by the `sales_channel_id` property of the `Cart` data model. -*** +### Retrieve with Query -## Invite Users +To retrieve the carts of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `carts.*` in `fields`: -To create a user, you can create an invite for them using the [createInvites method](https://docs.medusajs.com/references/user/createInvites/index.html.md) of the User Module's main service: +### query.graph ```ts -const invite = await userModuleService.createInvites({ - email: "user@example.com", +const { data: salesChannels } = await query.graph({ + entity: "sales_channel", + fields: [ + "carts.*", + ], }) + +// salesChannels.carts ``` -Later, you can accept the invite and create a new user for them: +### useQueryGraphStep ```ts -const invite = - await userModuleService.validateInviteToken("secret_123") +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -await userModuleService.updateInvites({ - id: invite.id, - accepted: true, -}) +// ... -const user = await userModuleService.createUsers({ - email: invite.email, +const { data: salesChannels } = useQueryGraphStep({ + entity: "sales_channel", + fields: [ + "carts.*", + ], }) + +// salesChannels.carts ``` -### Invite Expiry +*** -An invite has an expiry date. You can renew the expiry date and refresh the token using the [refreshInviteTokens method](https://docs.medusajs.com/references/user/refreshInviteTokens/index.html.md): - -```ts -await userModuleService.refreshInviteTokens(["invite_123"]) -``` +## Order Module -*** +Medusa defines a read-only link between the `SalesChannel` data model and the [Order Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/order/index.html.md)'s `Order` data model. This means you can retrieve the details of a sales channel's orders, but you don't manage the links in a pivot table in the database. The sales channel of an order is determined by the `sales_channel_id` property of the `Order` data model. -## Create Identity with the Auth Module +### Retrieve with Query -By combining the User and Auth Modules, you can use the Auth Module for authenticating users, and the User Module to manage those users. +To retrieve the orders of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `orders.*` in `fields`: -So, when a user is authenticated, and you receive the `AuthIdentity` object, you can use it to create a user if it doesn’t exist: +### query.graph ```ts -const { success, authIdentity } = - await authModuleService.authenticate("emailpass", { - // ... - }) - -const [, count] = await userModuleService.listAndCountUsers({ - email: authIdentity.entity_id, +const { data: salesChannels } = await query.graph({ + entity: "sales_channel", + fields: [ + "orders.*", + ], }) -if (!count) { - const user = await userModuleService.createUsers({ - email: authIdentity.entity_id, - }) -} +// salesChannels.orders ``` +### useQueryGraphStep -# Tax Module Options - -In this document, you'll learn about the options of the Tax Module. - -## providers - -The `providers` option is an array of either tax module providers or path to a file that defines a tax provider. - -When the Medusa application starts, these providers are registered and can be used to retrieve tax lines. - -```ts title="medusa-config.ts" -import { Modules } from "@medusajs/framework/utils" +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" // ... -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/tax", - options: { - providers: [ - { - resolve: "./path/to/my-provider", - id: "my-provider", - options: { - // ... - }, - }, - ], - }, - }, +const { data: salesChannels } = useQueryGraphStep({ + entity: "sales_channel", + fields: [ + "orders.*", ], }) + +// salesChannels.orders ``` -The objects in the array accept the following properties: +*** -- `resolve`: A string indicating the package name of the module provider or the path to it. -- `id`: A string indicating the provider's unique name or ID. -- `options`: An optional object of the module provider's options. +## Product Module +A product has different availability for different sales channels. Medusa defines a link between the `Product` and the `SalesChannel` data models. -# Tax Calculation with the Tax Provider +![A diagram showcasing an example of how resources from the Sales Channel and Product modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1709809833/Medusa%20Resources/product-sales-channel_t848ik.jpg) -In this document, you’ll learn how tax lines are calculated and what a tax provider is. +A product can be available in more than one sales channel. You can retrieve only the products of a sales channel. -## Tax Lines Calculation +### Retrieve with Query -Tax lines are calculated and retrieved using the [getTaxLines method of the Tax Module’s main service](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md). It accepts an array of line items and shipping methods, and the context of the calculation. +To retrieve the products of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `products.*` in `fields`: -For example: +### query.graph ```ts -const taxLines = await taxModuleService.getTaxLines( - [ - { - id: "cali_123", - product_id: "prod_123", - unit_price: 1000, - quantity: 1, - }, - { - id: "casm_123", - shipping_option_id: "so_123", - unit_price: 2000, - }, +const { data: salesChannels } = await query.graph({ + entity: "sales_channel", + fields: [ + "products.*", ], - { - address: { - country_code: "us", - }, - } -) +}) + +// salesChannels.products ``` -The context object is used to determine which tax regions and rates to use in the calculation. It includes properties related to the address and customer. +### useQueryGraphStep -The example above retrieves the tax lines based on the tax region for the United States. +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -The method returns tax lines for the line item and shipping methods. For example: +// ... -```json -[ - { - "line_item_id": "cali_123", - "rate_id": "txr_1", - "rate": 10, - "code": "XXX", - "name": "Tax Rate 1" - }, - { - "shipping_line_id": "casm_123", - "rate_id": "txr_2", - "rate": 5, - "code": "YYY", - "name": "Tax Rate 2" - } -] +const { data: salesChannels } = useQueryGraphStep({ + entity: "sales_channel", + fields: [ + "products.*", + ], +}) + +// salesChannels.products ``` -*** +### Manage with Link -## Using the Tax Provider in the Calculation +To manage the sales channels of a product, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -The tax lines retrieved by the `getTaxLines` method are actually retrieved from the tax region’s provider. +### link.create -A tax module provider whose main service implements the logic to shape tax lines. Each tax region has a tax provider. +```ts +import { Modules } from "@medusajs/framework/utils" -The Tax Module provides a `system` tax provider that only transforms calculated item and shipping tax rates into the required return type. +// ... -{/* --- +await link.create({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", + }, +}) +``` -TODO add once tax provider guide is updated + add module providers match other modules. +### createRemoteLinkStep -## Create Tax Provider +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -Refer to [this guide](/modules/tax/provider) to learn more about creating a tax provider. */} +// ... +createRemoteLinkStep({ + [Modules.PRODUCT]: { + product_id: "prod_123", + }, + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", + }, +}) +``` -# Tax Rates and Rules +*** -In this document, you’ll learn about tax rates and rules. +## Stock Location Module -## What are Tax Rates? +A stock location is associated with a sales channel. This scopes inventory quantities associated with that stock location by the associated sales channel. -A tax rate is a percentage amount used to calculate the tax amount for each taxable item’s price, such as line items or shipping methods, in a cart. The sum of all calculated tax amounts are then added to the cart’s total as a tax total. +Medusa defines a link between the `SalesChannel` and `StockLocation` data models. -Each tax region has a default tax rate. This tax rate is applied to all taxable items of a cart in that region. +![A diagram showcasing an example of how resources from the Sales Channel and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716796872/Medusa%20Resources/sales-channel-location_cqrih1.jpg) -### Combinable Tax Rates +### Retrieve with Query -Tax regions can have parent tax regions. To inherit the tax rates of the parent tax region, set the `is_combinable` of the child’s tax rates to `true`. +To retrieve the stock locations of a sales channel with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `stock_locations.*` in `fields`: -Then, when tax rates are retrieved for a taxable item in the child region, both the child and the parent tax regions’ applicable rates are returned. +### query.graph -*** +```ts +const { data: salesChannels } = await query.graph({ + entity: "sales_channel", + fields: [ + "stock_locations.*", + ], +}) -## Override Tax Rates with Rules +// salesChannels.stock_locations +``` -You can create tax rates that override the default for specific conditions or rules. +### useQueryGraphStep -For example, you can have a default tax rate is 10%, but for products of type “Shirt” is %15. +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -A tax region can have multiple tax rates, and each tax rate can have multiple tax rules. The [TaxRateRule data model](https://docs.medusajs.com/references/tax/models/TaxRateRule/index.html.md) represents a tax rate’s rule. +// ... -![A diagram showcasing the relation between TaxRegion, TaxRate, and TaxRateRule](https://res.cloudinary.com/dza7lstvk/image/upload/v1711462167/Medusa%20Resources/tax-rate-rule_enzbp2.jpg) +const { data: salesChannels } = useQueryGraphStep({ + entity: "sales_channel", + fields: [ + "stock_locations.*", + ], +}) -These two properties of the data model identify the rule’s target: +// salesChannels.stock_locations +``` -- `reference`: the name of the table in the database that this rule points to. For example, `product_type`. -- `reference_id`: the ID of the data model’s record that this points to. For example, a product type’s ID. +### Manage with Link -So, to override the default tax rate for product types “Shirt”, you create a tax rate and associate with it a tax rule whose `reference` is `product_type` and `reference_id` the ID of the “Shirt” product type. +To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): +### link.create -# Tax Region +```ts +import { Modules } from "@medusajs/framework/utils" -In this document, you’ll learn about tax regions and how to use them with the Region Module. +// ... -## What is a Tax Region? +await link.create({ + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", + }, + [Modules.STOCK_LOCATION]: { + sales_channel_id: "sloc_123", + }, +}) +``` -A tax region, represented by the [TaxRegion data model](https://docs.medusajs.com/references/tax/models/TaxRegion/index.html.md), stores tax settings related to a region that your store serves. +### createRemoteLinkStep -Tax regions can inherit settings and rules from a parent tax region. +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -Each tax region has tax rules and a tax provider. +// ... +createRemoteLinkStep({ + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", + }, + [Modules.STOCK_LOCATION]: { + sales_channel_id: "sloc_123", + }, +}) +``` -# Emailpass Auth Module Provider - -In this document, you’ll learn about the Emailpass auth module provider and how to install and use it in the Auth Module. - -Using the Emailpass auth module provider, you allow users to register and login with an email and password. - -*** -## Register the Emailpass Auth Module Provider +# Publishable API Keys with Sales Channels -The Emailpass auth provider is registered by default with the Auth Module. +In this document, you’ll learn what publishable API keys are and how to use them with sales channels. -If you want to pass options to the provider, add the provider to the `providers` option of the Auth Module: +## Publishable API Keys with Sales Channels -```ts title="medusa-config.ts" -import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils" +A publishable API key, provided by the API Key Module, is a client key scoped to one or more sales channels. -// ... +When sending a request to a Store API route, you must pass a publishable API key in the header of the request: -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/medusa/auth", - dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER], - options: { - providers: [ - // other providers... - { - resolve: "@medusajs/medusa/auth-emailpass", - id: "emailpass", - options: { - // options... - }, - }, - ], - }, - }, - ], -}) +```bash +curl http://localhost:9000/store/products \ + x-publishable-api-key: {your_publishable_api_key} ``` -### Module Options - -|Configuration|Description|Required|Default| -|---|---|---|---|---|---|---| -|\`hashConfig\`|An object of configurations to use when hashing the user's -password. Refer to |No|\`\`\`ts -const hashConfig = \{ - logN: 15, - r: 8, - p: 1 -} -\`\`\`| +The Medusa application infers the associated sales channels and ensures that only data relevant to the sales channel are used. *** -## Related Guides +## How to Create a Publishable API Key? -- [How to register a customer using email and password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md) +To create a publishable API key, either use the Medusa Admin or the [Admin API Routes](https://docs.medusajs.com/api/admin#publishable-api-keys). -# GitHub Auth Module Provider +# Stock Location Concepts -In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module. +In this document, you’ll learn about the main concepts in the Stock Location Module. -The Github Auth Module Provider authenticates users with their GitHub account. +## Stock Location -Learn about the authentication flow in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md). +A stock location, represented by the `StockLocation` data model, represents a location where stock items are kept. For example, a warehouse. + +Medusa uses stock locations to provide inventory details, from the Inventory Module, per location. *** -## Register the Github Auth Module Provider +## StockLocationAddress -### Prerequisites +The `StockLocationAddress` data model belongs to the `StockLocation` data model. It provides more detailed information of the location, such as country code or street address. -- [Register GitHub App. When setting the Callback URL, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app) -- [Retrieve the client ID and client secret of your GitHub App](https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28#using-basic-authentication) -Add the module to the array of providers passed to the Auth Module: +# Links between Stock Location Module and Other Modules -```ts title="medusa-config.ts" -import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils" +This document showcases the module links defined between the Stock Location Module and other commerce modules. -// ... +## Summary -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/medusa/auth", - dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER], - options: { - providers: [ - // other providers... - { - resolve: "@medusajs/medusa/auth-github", - id: "github", - options: { - clientId: process.env.GITHUB_CLIENT_ID, - clientSecret: process.env.GITHUB_CLIENT_SECRET, - callbackUrl: process.env.GITHUB_CALLBACK_URL, - }, - }, - ], - }, - }, - ], -}) -``` +The Stock Location Module has the following links to other modules: -### Environment Variables +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. -Make sure to add the necessary environment variables for the above options in `.env`: +- [`FulfillmentSet` data model of the Fulfillment Module \<> `StockLocation` data model](#fulfillment-module). +- [`FulfillmentProvider` data model of the Fulfillment Module \<> `StockLocation` data model](#fulfillment-module). +- [`StockLocation` data model \<> `Inventory` data model of the Inventory Module](#inventory-module). +- [`SalesChannel` data model of the Sales Channel Module \<> `StockLocation` data model](#sales-channel-module). -```plain -GITHUB_CLIENT_ID= -GITHUB_CLIENT_SECRET= -GITHUB_CALLBACK_URL= -``` +*** -### Module Options +## Fulfillment Module -|Configuration|Description|Required| -|---|---|---|---|---| -|\`clientId\`|A string indicating the client ID of your GitHub app.|Yes| -|\`clientSecret\`|A string indicating the client secret of your GitHub app.|Yes| -|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in GitHub.|Yes| +A fulfillment set can be conditioned to a specific stock location. -*** +Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models. -## Override Callback URL During Authentication +![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1712567101/Medusa%20Resources/fulfillment-stock-location_nlkf7e.jpg) -In many cases, you may have different callback URL for actor types. For example, you may redirect admin users to a different URL than customers after authentication. +Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location. -The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md) can accept a `callback_url` body parameter to override the provider's `callbackUrl` option. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md). +![A diagram showcasing an example of how data models from the Fulfillment and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1728399492/Medusa%20Resources/fulfillment-provider-stock-location_b0mulo.jpg) -*** +### Retrieve with Query -## Examples +To retrieve the fulfillment sets of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `fulfillment_sets.*` in `fields`: -- [How to implement third-party / social login in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md). +To retrieve the fulfillment providers, pass `fulfillment_providers.*` in `fields`. +### query.graph -# Google Auth Module Provider +```ts +const { data: stockLocations } = await query.graph({ + entity: "stock_location", + fields: [ + "fulfillment_sets.*", + ], +}) -In this document, you’ll learn about the Google Auth Module Provider and how to install and use it in the Auth Module. +// stockLocations.fulfillment_sets +``` -The Google Auth Module Provider authenticates users with their Google account. +### useQueryGraphStep -Learn about the authentication flow for third-party providers in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#2-third-party-service-authenticate-flow/index.html.md). +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -*** +// ... -## Register the Google Auth Module Provider +const { data: stockLocations } = useQueryGraphStep({ + entity: "stock_location", + fields: [ + "fulfillment_sets.*", + ], +}) -### Prerequisites +// stockLocations.fulfillment_sets +``` -- [Create a project in Google Cloud.](https://cloud.google.com/resource-manager/docs/creating-managing-projects) -- [Create authorization credentials. When setting the Redirect Uri, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred) +### Manage with Link -Add the module to the array of providers passed to the Auth Module: +To manage the stock location of a fulfillment set, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -```ts title="medusa-config.ts" -import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils" +### link.create + +```ts +import { Modules } from "@medusajs/framework/utils" // ... -module.exports = defineConfig({ - // ... - modules: [ - { - // ... - [Modules.AUTH]: { - resolve: "@medusajs/medusa/auth", - dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER], - options: { - providers: [ - // other providers... - { - resolve: "@medusajs/medusa/auth-google", - id: "google", - options: { - clientId: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - callbackUrl: process.env.GOOGLE_CALLBACK_URL, - }, - }, - ], - }, - }, - }, - ], +await link.create({ + [Modules.STOCK_LOCATION]: { + stock_location_id: "sloc_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_set_id: "fset_123", + }, }) ``` -### Environment Variables - -Make sure to add the necessary environment variables for the above options in `.env`: +### createRemoteLinkStep -```plain -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= -GOOGLE_CALLBACK_URL= -``` +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -### Module Options +// ... -|Configuration|Description|Required| -|---|---|---|---|---| -|\`clientId\`|A string indicating the |Yes| -|\`clientSecret\`|A string indicating the |Yes| -|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in Google.|Yes| +createRemoteLinkStep({ + [Modules.STOCK_LOCATION]: { + stock_location_id: "sloc_123", + }, + [Modules.FULFILLMENT]: { + fulfillment_set_id: "fset_123", + }, +}) +``` *** -*** +## Inventory Module -## Override Callback URL During Authentication +Medusa defines a read-only link between the `StockLocation` data model and the [Inventory Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/inventory/index.html.md)'s `InventoryLevel` data model. This means you can retrieve the details of a stock location's inventory levels, but you don't manage the links in a pivot table in the database. The stock location of an inventory level is determined by the `location_id` property of the `InventoryLevel` data model. -In many cases, you may have different callback URL for actor types. For example, you may redirect admin users to a different URL than customers after authentication. +### Retrieve with Query -The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md) can accept a `callback_url` body parameter to override the provider's `callbackUrl` option. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md). +To retrieve the inventory levels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `inventory_levels.*` in `fields`: -*** +### query.graph -## Examples +```ts +const { data: stockLocations } = await query.graph({ + entity: "stock_location", + fields: [ + "inventory_levels.*", + ], +}) -- [How to implement Google social login in the storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md). +// stockLocations.inventory_levels +``` +### useQueryGraphStep -# Stripe Module Provider +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -In this document, you’ll learn about the Stripe Module Provider and how to configure it in the Payment Module. +// ... -## Register the Stripe Module Provider +const { data: stockLocations } = useQueryGraphStep({ + entity: "stock_location", + fields: [ + "inventory_levels.*", + ], +}) -### Prerequisites +// stockLocations.inventory_levels +``` -- [Stripe account](https://stripe.com/) -- [Stripe Secret API Key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard) -- [For deployed Medusa applications, a Stripe webhook secret. Refer to the end of this guide for details on the URL and events.](https://docs.stripe.com/webhooks#add-a-webhook-endpoint) +*** -The Stripe Module Provider is installed by default in your application. To use it, add it to the array of providers passed to the Payment Module in `medusa-config.ts`: +## Sales Channel Module -```ts title="medusa-config.ts" -import { Modules } from "@medusajs/framework/utils" +A stock location is associated with a sales channel. This scopes inventory quantities in a stock location by the associated sales channel. -// ... +Medusa defines a link between the `SalesChannel` and `StockLocation` data models. -module.exports = defineConfig({ - // ... - modules: [ - { - resolve: "@medusajs/medusa/payment", - options: { - providers: [ - { - resolve: "@medusajs/medusa/payment-stripe", - id: "stripe", - options: { - apiKey: process.env.STRIPE_API_KEY, - }, - }, - ], - }, - }, +![A diagram showcasing an example of how resources from the Sales Channel and Stock Location modules are linked](https://res.cloudinary.com/dza7lstvk/image/upload/v1716796872/Medusa%20Resources/sales-channel-location_cqrih1.jpg) + +### Retrieve with Query + +To retrieve the sales channels of a stock location with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `sales_channels.*` in `fields`: + +### query.graph + +```ts +const { data: stockLocations } = await query.graph({ + entity: "stock_location", + fields: [ + "sales_channels.*", ], }) + +// stockLocations.sales_channels ``` -### Environment Variables +### useQueryGraphStep -Make sure to add the necessary environment variables for the above options in `.env`: +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -```bash -STRIPE_API_KEY= +// ... + +const { data: stockLocations } = useQueryGraphStep({ + entity: "stock_location", + fields: [ + "sales_channels.*", + ], +}) + +// stockLocations.sales_channels ``` -### Module Options +### Manage with Link -|Option|Description|Required|Default| -|---|---|---|---|---|---|---| -|\`apiKey\`|A string indicating the Stripe Secret API key.|Yes|-| -|\`webhookSecret\`|A string indicating the Stripe webhook secret. This is only useful for deployed Medusa applications.|Yes|-| -|\`capture\`|Whether to automatically capture payment after authorization.|No|\`false\`| -|\`automatic\_payment\_methods\`|A boolean value indicating whether to enable Stripe's automatic payment methods. This is useful if you integrate services like Apple pay or Google pay.|No|\`false\`| -|\`payment\_description\`|A string used as the default description of a payment if none is available in cart.context.payment\_description.|No|-| +To manage the stock locations of a sales channel, use [Link](https://docs.medusajs.com/docs/learn/fundamentals/module-links/link/index.html.md): -*** +### link.create -## Setup Stripe Webhooks +```ts +import { Modules } from "@medusajs/framework/utils" -For production applications, you must set up webhooks in Stripe that inform Medusa of changes and updates to payments. Refer to [Stripe's documentation](https://docs.stripe.com/webhooks#add-a-webhook-endpoint) on how to setup webhooks. +// ... -### Webhook URL +await link.create({ + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", + }, + [Modules.STOCK_LOCATION]: { + sales_channel_id: "sloc_123", + }, +}) +``` -Medusa has a `{server_url}/hooks/payment/{provider_id}` API route that you can use to register webhooks in Stripe, where: +### createRemoteLinkStep -- `{server_url}` is the URL to your deployed Medusa application in server mode. -- `{provider_id}` is the ID of the provider, such as `stripe_stripe` for basic payments. +```ts +import { Modules } from "@medusajs/framework/utils" +import { createRemoteLinkStep } from "@medusajs/medusa/core-flows" -The Stripe Module Provider supports the following payment types, and the webhook endpoint URL is different for each: +// ... -|Stripe Payment Type|Webhook Endpoint URL| -|---|---|---| -|Basic Stripe Payment|\`\{server\_url}/hooks/payment/stripe\_stripe\`| -|Bancontact Payments|\`\{server\_url}/hooks/payment/stripe-bancontact\_stripe\`| -|BLIK Payments|\`\{server\_url}/hooks/payment/stripe-blik\_stripe\`| -|giropay Payments|\`\{server\_url}/hooks/payment/stripe-giropay\_stripe\`| -|iDEAL Payments|\`\{server\_url}/hooks/payment/stripe-ideal\_stripe\`| -|Przelewy24 Payments|\`\{server\_url}/hooks/payment/stripe-przelewy24\_stripe\`| -|PromptPay Payments|\`\{server\_url}/hooks/payment/stripe-promptpay\_stripe\`| +createRemoteLinkStep({ + [Modules.SALES_CHANNEL]: { + sales_channel_id: "sc_123", + }, + [Modules.STOCK_LOCATION]: { + sales_channel_id: "sloc_123", + }, +}) +``` -### Webhook Events -When you set up the webhook in Stripe, choose the following events to listen to: +# Links between Store Module and Other Modules -- `payment_intent.amount_capturable_updated` -- `payment_intent.succeeded` -- `payment_intent.payment_failed` +This document showcases the module links defined between the Store Module and other commerce modules. -*** +## Summary -## Useful Guides +The Store Module has the following links to other modules: -- [Storefront guide: Add Stripe payment method during checkout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/stripe/index.html.md). -- [Integrate in Next.js Starter](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter#stripe-integration/index.html.md). -- [Customize Stripe Integration in Next.js Starter](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/guides/customize-stripe/index.html.md). +Read-only links are used to query data across modules, but the relations aren't stored in a pivot table in the database. +- [`Currency` data model \<> `Currency` data model of Currency Module](#currency-module). (Read-only). -# Get Product Variant Prices using Query +*** -In this document, you'll learn how to retrieve product variant prices in the Medusa application using [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md). +## Currency Module -The Product Module doesn't provide pricing functionalities. The Medusa application links the Product Module's `ProductVariant` data model to the Pricing Module's `PriceSet` data model. +The Store Module has a `Currency` data model that stores the supported currencies of a store. However, these currencies don't hold all the details of a currency, such as its name or symbol. -So, to retrieve data across the linked records of the two modules, you use Query. +Instead, Medusa defines a read-only link between the [Currency Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/currency/index.html.md)'s `Currency` data model and the Store Module's `Currency` data model. This means you can retrieve the details of a store's supported currencies, but you don't manage the links in a pivot table in the database. The currencies of a store are determined by the `currency_code` of the `Currency` data model in the Store Module. -## Retrieve All Product Variant Prices +### Retrieve with Query -To retrieve all product variant prices, retrieve the product using Query and include among its fields `variants.prices.*`. +To retrieve the details of a store's currencies with [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md), pass `supported_currencies.currency.*` in `fields`: -For example: +### query.graph -```ts highlights={[["6"]]} -const { data: products } = await query.graph({ - entity: "product", +```ts +const { data: stores } = await query.graph({ + entity: "store", fields: [ - "*", - "variants.*", - "variants.prices.*", + "supported_currencies.currency.*", ], - filters: { - id: [ - "prod_123", - ], - }, }) + +// stores.supported_currencies ``` -Each variant in the retrieved products has a `prices` array property with all the product variant prices. Each price object has the properties of the [Pricing Module's Price data model](https://docs.medusajs.com/references/pricing/models/Price/index.html.md). +### useQueryGraphStep -*** +```ts +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" -## Retrieve Calculated Price for a Context +// ... -The Pricing Module can calculate prices of a variant based on a [context](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), such as the region ID or the currency code. +const { data: stores } = useQueryGraphStep({ + entity: "store", + fields: [ + "supported_currencies.currency.*", + ], +}) -Learn more about prices calculation in [this Pricing Module documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md). +// stores.supported_currencies +``` -To retrieve calculated prices of variants based on a context, retrieve the products using Query and: -- Pass `variants.calculated_price.*` in the `fields` property. -- Pass a `context` property in the object parameter. Its value is an object of objects that sets the context for the retrieved fields. +# User Module Options -For example: +In this document, you'll learn about the options of the User Module. -```ts highlights={[["10"], ["15"], ["16"], ["17"], ["18"], ["19"], ["20"], ["21"], ["22"]]} -import { QueryContext } from "@medusajs/framework/utils" +## Module Options + +```ts title="medusa-config.ts" +import { Modules } from "@medusajs/framework/utils" // ... -const { data: products } = await query.graph({ - entity: "product", - fields: [ - "*", - "variants.*", - "variants.calculated_price.*", - ], - filters: { - id: "prod_123", - }, - context: { - variants: { - calculated_price: QueryContext({ - region_id: "reg_01J3MRPDNXXXDSCC76Y6YCZARS", - currency_code: "eur", - }), +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/user", + options: { + jwt_secret: process.env.JWT_SECRET, + }, }, - }, + ], }) ``` -For the context of the product variant's calculated price, you pass an object to `context` with the property `variants`, whose value is another object with the property `calculated_price`. +|Option|Description|Required| +|---|---|---|---|---| +|\`jwt\_secret\`|A string indicating the secret used to sign the invite tokens.|Yes| -`calculated_price`'s value is created using `QueryContext` from the Modules SDK, passing it a [calculation context object](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md). +### Environment Variables -Each variant in the retrieved products has a `calculated_price` object. Learn more about its properties in [this Pricing Module guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md). +Make sure to add the necessary environment variables for the above options in `.env`: +```bash +JWT_SECRET=supersecret +``` -# Calculate Product Variant Price with Taxes -In this document, you'll learn how to calculate a product variant's price with taxes. +# User Creation Flows -## Step 0: Resolve Resources +In this document, learn the different ways to create a user using the User Module. -You'll need the following resources for the taxes calculation: +## Straightforward User Creation -1. [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) to retrieve the product's variants' prices for a context. Learn more about that in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/guides/price/index.html.md). -2. The Tax Module's main service to get the tax lines for each product. +To create a user, use the [create method of the User Module’s main service](https://docs.medusajs.com/references/user/create/index.html.md): ```ts -// other imports... -import { - Modules, - ContainerRegistrationKeys, -} from "@medusajs/framework/utils" - -// In an API route, workflow step, etc... -const query = container.resolve(ContainerRegistrationKeys.QUERY) -const taxModuleService = container.resolve( - Modules.TAX -) +const user = await userModuleService.createUsers({ + email: "user@example.com", +}) ``` +You can pair this with the Auth Module to allow the user to authenticate, as explained in a [later section](#create-identity-with-the-auth-module). + *** -## Step 1: Retrieve Prices for a Context +## Invite Users -After resolving the resources, use Query to retrieve the products with the variants' prices for a context: +To create a user, you can create an invite for them using the [createInvites method](https://docs.medusajs.com/references/user/createInvites/index.html.md) of the User Module's main service: -Learn more about retrieving product variants' prices for a context in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/guides/price/index.html.md). +```ts +const invite = await userModuleService.createInvites({ + email: "user@example.com", +}) +``` + +Later, you can accept the invite and create a new user for them: ```ts -import { QueryContext } from "@medusajs/framework/utils" +const invite = + await userModuleService.validateInviteToken("secret_123") -// ... +await userModuleService.updateInvites({ + id: invite.id, + accepted: true, +}) -const { data: products } = await query.graph({ - entity: "product", - fields: [ - "*", - "variants.*", - "variants.calculated_price.*", - ], - filters: { - id: "prod_123", - }, - context: { - variants: { - calculated_price: QueryContext({ - region_id: "region_123", - currency_code: "usd", - }), - }, - }, +const user = await userModuleService.createUsers({ + email: invite.email, }) ``` +### Invite Expiry + +An invite has an expiry date. You can renew the expiry date and refresh the token using the [refreshInviteTokens method](https://docs.medusajs.com/references/user/refreshInviteTokens/index.html.md): + +```ts +await userModuleService.refreshInviteTokens(["invite_123"]) +``` + *** -## Step 2: Get Tax Lines for Products +## Create Identity with the Auth Module -To retrieve the tax line of each product, first, add the following utility method: +By combining the User and Auth Modules, you can use the Auth Module for authenticating users, and the User Module to manage those users. + +So, when a user is authenticated, and you receive the `AuthIdentity` object, you can use it to create a user if it doesn’t exist: ```ts -// other imports... -import { - HttpTypes, - TaxableItemDTO, -} from "@medusajs/framework/types" +const { success, authIdentity } = + await authModuleService.authenticate("emailpass", { + // ... + }) -// ... -const asTaxItem = (product: HttpTypes.StoreProduct): TaxableItemDTO[] => { - return product.variants - ?.map((variant) => { - if (!variant.calculated_price) { - return - } +const [, count] = await userModuleService.listAndCountUsers({ + email: authIdentity.entity_id, +}) - return { - id: variant.id, - product_id: product.id, - product_name: product.title, - product_categories: product.categories?.map((c) => c.name), - product_category_id: product.categories?.[0]?.id, - product_sku: variant.sku, - product_type: product.type, - product_type_id: product.type_id, - quantity: 1, - unit_price: variant.calculated_price.calculated_amount, - currency_code: variant.calculated_price.currency_code, - } - }) - .filter((v) => !!v) as unknown as TaxableItemDTO[] +if (!count) { + const user = await userModuleService.createUsers({ + email: authIdentity.entity_id, + }) } ``` -This formats the products as items to calculate tax lines for. -Then, use it when retrieving the tax lines of the products retrieved earlier: +# Tax Calculation with the Tax Provider -```ts -// other imports... -import { - ItemTaxLineDTO, -} from "@medusajs/framework/types" +In this document, you’ll learn how tax lines are calculated and what a tax provider is. -// ... -const taxLines = (await taxModuleService.getTaxLines( - products.map(asTaxItem).flat(), +## Tax Lines Calculation + +Tax lines are calculated and retrieved using the [getTaxLines method of the Tax Module’s main service](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md). It accepts an array of line items and shipping methods, and the context of the calculation. + +For example: + +```ts +const taxLines = await taxModuleService.getTaxLines( + [ + { + id: "cali_123", + product_id: "prod_123", + unit_price: 1000, + quantity: 1, + }, + { + id: "casm_123", + shipping_option_id: "so_123", + unit_price: 2000, + }, + ], { - // example of context properties. You can pass other ones. address: { - country_code, + country_code: "us", }, } -)) as unknown as ItemTaxLineDTO[] +) ``` -You use the Tax Module's main service's [getTaxLines method](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md) to retrieve the tax line. +The context object is used to determine which tax regions and rates to use in the calculation. It includes properties related to the address and customer. -For the first parameter, you use the `asTaxItem` function to format the products as expected by the `getTaxLines` method. +The example above retrieves the tax lines based on the tax region for the United States. -For the second parameter, you pass the current context. You can pass other details such as the customer's ID. +The method returns tax lines for the line item and shipping methods. For example: -Learn about the other context properties to pass in [the getTaxLines method's reference](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md). +```json +[ + { + "line_item_id": "cali_123", + "rate_id": "txr_1", + "rate": 10, + "code": "XXX", + "name": "Tax Rate 1" + }, + { + "shipping_line_id": "casm_123", + "rate_id": "txr_2", + "rate": 5, + "code": "YYY", + "name": "Tax Rate 2" + } +] +``` *** -## Step 3: Calculate Price with Tax for Variant +## Using the Tax Provider in the Calculation -To calculate the price with and without taxes for a variant, first, group the tax lines retrieved in the previous step by variant IDs: +The tax lines retrieved by the `getTaxLines` method are actually retrieved from the tax region’s provider. -```ts highlights={taxLineHighlights} -const taxLinesMap = new Map() -taxLines.forEach((taxLine) => { - const variantId = taxLine.line_item_id - if (!taxLinesMap.has(variantId)) { - taxLinesMap.set(variantId, []) - } +A tax module provider whose main service implements the logic to shape tax lines. Each tax region has a tax provider. - taxLinesMap.get(variantId)?.push(taxLine) -}) -``` +The Tax Module provides a `system` tax provider that only transforms calculated item and shipping tax rates into the required return type. -Notice that the variant's ID is stored in the `line_item_id` property of a tax line since tax lines are used for line items in a cart. +{/* --- -Then, loop over the products and their variants to retrieve the prices with and without taxes: +TODO add once tax provider guide is updated + add module providers match other modules. -```ts highlights={calculateTaxHighlights} -// other imports... -import { - calculateAmountsWithTax, -} from "@medusajs/framework/utils" +## Create Tax Provider -// ... -products.forEach((product) => { - product.variants?.forEach((variant) => { - if (!variant.calculated_price) { - return - } +Refer to [this guide](/modules/tax/provider) to learn more about creating a tax provider. */} - const taxLinesForVariant = taxLinesMap.get(variant.id) || [] - const { priceWithTax, priceWithoutTax } = calculateAmountsWithTax({ - taxLines: taxLinesForVariant, - amount: variant.calculated_price!.calculated_amount!, - includesTax: - variant.calculated_price!.is_calculated_price_tax_inclusive!, - }) - // do something with prices... - }) -}) -``` +# Tax Module Options -For each product variant, you: +In this document, you'll learn about the options of the Tax Module. -1. Retrieve its tax lines from the `taxLinesMap`. -2. Calculate its prices with and without taxes using the `calculateAmountsWithTax` from the Medusa Framework. -3. The `calculateAmountsWithTax` function returns an object having two properties: - - `priceWithTax`: The variant's price with the taxes applied. - - `priceWithoutTax`: The variant's price without taxes applied. +## providers +The `providers` option is an array of either tax module providers or path to a file that defines a tax provider. -## Workflows +When the Medusa application starts, these providers are registered and can be used to retrieve tax lines. -- [createApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/createApiKeysWorkflow/index.html.md) -- [deleteApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteApiKeysWorkflow/index.html.md) -- [linkSalesChannelsToApiKeyWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToApiKeyWorkflow/index.html.md) -- [revokeApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/revokeApiKeysWorkflow/index.html.md) -- [updateApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateApiKeysWorkflow/index.html.md) -- [addShippingMethodToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addShippingMethodToCartWorkflow/index.html.md) -- [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md) -- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md) -- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/index.html.md) -- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/index.html.md) -- [createPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentCollectionForCartWorkflow/index.html.md) -- [listShippingOptionsForCartWithPricingWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWithPricingWorkflow/index.html.md) -- [listShippingOptionsForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWorkflow/index.html.md) -- [refreshCartItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartItemsWorkflow/index.html.md) -- [refreshPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshPaymentCollectionForCartWorkflow/index.html.md) -- [transferCartCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/transferCartCustomerWorkflow/index.html.md) -- [updateCartPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartPromotionsWorkflow/index.html.md) +```ts title="medusa-config.ts" +import { Modules } from "@medusajs/framework/utils" + +// ... + +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/tax", + options: { + providers: [ + { + resolve: "./path/to/my-provider", + id: "my-provider", + options: { + // ... + }, + }, + ], + }, + }, + ], +}) +``` + +The objects in the array accept the following properties: + +- `resolve`: A string indicating the package name of the module provider or the path to it. +- `id`: A string indicating the provider's unique name or ID. +- `options`: An optional object of the module provider's options. + + +# Tax Rates and Rules + +In this document, you’ll learn about tax rates and rules. + +## What are Tax Rates? + +A tax rate is a percentage amount used to calculate the tax amount for each taxable item’s price, such as line items or shipping methods, in a cart. The sum of all calculated tax amounts are then added to the cart’s total as a tax total. + +Each tax region has a default tax rate. This tax rate is applied to all taxable items of a cart in that region. + +### Combinable Tax Rates + +Tax regions can have parent tax regions. To inherit the tax rates of the parent tax region, set the `is_combinable` of the child’s tax rates to `true`. + +Then, when tax rates are retrieved for a taxable item in the child region, both the child and the parent tax regions’ applicable rates are returned. + +*** + +## Override Tax Rates with Rules + +You can create tax rates that override the default for specific conditions or rules. + +For example, you can have a default tax rate is 10%, but for products of type “Shirt” is %15. + +A tax region can have multiple tax rates, and each tax rate can have multiple tax rules. The [TaxRateRule data model](https://docs.medusajs.com/references/tax/models/TaxRateRule/index.html.md) represents a tax rate’s rule. + +![A diagram showcasing the relation between TaxRegion, TaxRate, and TaxRateRule](https://res.cloudinary.com/dza7lstvk/image/upload/v1711462167/Medusa%20Resources/tax-rate-rule_enzbp2.jpg) + +These two properties of the data model identify the rule’s target: + +- `reference`: the name of the table in the database that this rule points to. For example, `product_type`. +- `reference_id`: the ID of the data model’s record that this points to. For example, a product type’s ID. + +So, to override the default tax rate for product types “Shirt”, you create a tax rate and associate with it a tax rule whose `reference` is `product_type` and `reference_id` the ID of the “Shirt” product type. + + +# Tax Region + +In this document, you’ll learn about tax regions and how to use them with the Region Module. + +## What is a Tax Region? + +A tax region, represented by the [TaxRegion data model](https://docs.medusajs.com/references/tax/models/TaxRegion/index.html.md), stores tax settings related to a region that your store serves. + +Tax regions can inherit settings and rules from a parent tax region. + +Each tax region has tax rules and a tax provider. + + +# Emailpass Auth Module Provider + +In this document, you’ll learn about the Emailpass auth module provider and how to install and use it in the Auth Module. + +Using the Emailpass auth module provider, you allow users to register and login with an email and password. + +*** + +## Register the Emailpass Auth Module Provider + +The Emailpass auth provider is registered by default with the Auth Module. + +If you want to pass options to the provider, add the provider to the `providers` option of the Auth Module: + +```ts title="medusa-config.ts" +import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils" + +// ... + +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/auth", + dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER], + options: { + providers: [ + // other providers... + { + resolve: "@medusajs/medusa/auth-emailpass", + id: "emailpass", + options: { + // options... + }, + }, + ], + }, + }, + ], +}) +``` + +### Module Options + +|Configuration|Description|Required|Default| +|---|---|---|---|---|---|---| +|\`hashConfig\`|An object of configurations to use when hashing the user's +password. Refer to |No|\`\`\`ts +const hashConfig = \{ + logN: 15, + r: 8, + p: 1 +} +\`\`\`| + +*** + +## Related Guides + +- [How to register a customer using email and password](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/register/index.html.md) + + +# Google Auth Module Provider + +In this document, you’ll learn about the Google Auth Module Provider and how to install and use it in the Auth Module. + +The Google Auth Module Provider authenticates users with their Google account. + +Learn about the authentication flow for third-party providers in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#2-third-party-service-authenticate-flow/index.html.md). + +*** + +## Register the Google Auth Module Provider + +### Prerequisites + +- [Create a project in Google Cloud.](https://cloud.google.com/resource-manager/docs/creating-managing-projects) +- [Create authorization credentials. When setting the Redirect Uri, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred) + +Add the module to the array of providers passed to the Auth Module: + +```ts title="medusa-config.ts" +import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils" + +// ... + +module.exports = defineConfig({ + // ... + modules: [ + { + // ... + [Modules.AUTH]: { + resolve: "@medusajs/medusa/auth", + dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER], + options: { + providers: [ + // other providers... + { + resolve: "@medusajs/medusa/auth-google", + id: "google", + options: { + clientId: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + callbackUrl: process.env.GOOGLE_CALLBACK_URL, + }, + }, + ], + }, + }, + }, + ], +}) +``` + +### Environment Variables + +Make sure to add the necessary environment variables for the above options in `.env`: + +```plain +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +GOOGLE_CALLBACK_URL= +``` + +### Module Options + +|Configuration|Description|Required| +|---|---|---|---|---| +|\`clientId\`|A string indicating the |Yes| +|\`clientSecret\`|A string indicating the |Yes| +|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in Google.|Yes| + +*** + +*** + +## Override Callback URL During Authentication + +In many cases, you may have different callback URL for actor types. For example, you may redirect admin users to a different URL than customers after authentication. + +The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md) can accept a `callback_url` body parameter to override the provider's `callbackUrl` option. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md). + +*** + +## Examples + +- [How to implement Google social login in the storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md). + + +# GitHub Auth Module Provider + +In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module. + +The Github Auth Module Provider authenticates users with their GitHub account. + +Learn about the authentication flow in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route/index.html.md). + +*** + +## Register the Github Auth Module Provider + +### Prerequisites + +- [Register GitHub App. When setting the Callback URL, set it to a URL in your frontend that later uses Medusa's callback route to validate the authentication.](https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app) +- [Retrieve the client ID and client secret of your GitHub App](https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28#using-basic-authentication) + +Add the module to the array of providers passed to the Auth Module: + +```ts title="medusa-config.ts" +import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils" + +// ... + +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/auth", + dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER], + options: { + providers: [ + // other providers... + { + resolve: "@medusajs/medusa/auth-github", + id: "github", + options: { + clientId: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + callbackUrl: process.env.GITHUB_CALLBACK_URL, + }, + }, + ], + }, + }, + ], +}) +``` + +### Environment Variables + +Make sure to add the necessary environment variables for the above options in `.env`: + +```plain +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +GITHUB_CALLBACK_URL= +``` + +### Module Options + +|Configuration|Description|Required| +|---|---|---|---|---| +|\`clientId\`|A string indicating the client ID of your GitHub app.|Yes| +|\`clientSecret\`|A string indicating the client secret of your GitHub app.|Yes| +|\`callbackUrl\`|A string indicating the URL to redirect to in your frontend after the user completes their authentication in GitHub.|Yes| + +*** + +## Override Callback URL During Authentication + +In many cases, you may have different callback URL for actor types. For example, you may redirect admin users to a different URL than customers after authentication. + +The [Authenticate or Login API Route](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md) can accept a `callback_url` body parameter to override the provider's `callbackUrl` option. Learn more in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/auth/authentication-route#login-route/index.html.md). + +*** + +## Examples + +- [How to implement third-party / social login in the storefront.](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/customers/third-party-login/index.html.md). + + +# Stripe Module Provider + +In this document, you’ll learn about the Stripe Module Provider and how to configure it in the Payment Module. + +## Register the Stripe Module Provider + +### Prerequisites + +- [Stripe account](https://stripe.com/) +- [Stripe Secret API Key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard) +- [For deployed Medusa applications, a Stripe webhook secret. Refer to the end of this guide for details on the URL and events.](https://docs.stripe.com/webhooks#add-a-webhook-endpoint) + +The Stripe Module Provider is installed by default in your application. To use it, add it to the array of providers passed to the Payment Module in `medusa-config.ts`: + +```ts title="medusa-config.ts" +import { Modules } from "@medusajs/framework/utils" + +// ... + +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "@medusajs/medusa/payment", + options: { + providers: [ + { + resolve: "@medusajs/medusa/payment-stripe", + id: "stripe", + options: { + apiKey: process.env.STRIPE_API_KEY, + }, + }, + ], + }, + }, + ], +}) +``` + +### Environment Variables + +Make sure to add the necessary environment variables for the above options in `.env`: + +```bash +STRIPE_API_KEY= +``` + +### Module Options + +|Option|Description|Required|Default| +|---|---|---|---|---|---|---| +|\`apiKey\`|A string indicating the Stripe Secret API key.|Yes|-| +|\`webhookSecret\`|A string indicating the Stripe webhook secret. This is only useful for deployed Medusa applications.|Yes|-| +|\`capture\`|Whether to automatically capture payment after authorization.|No|\`false\`| +|\`automatic\_payment\_methods\`|A boolean value indicating whether to enable Stripe's automatic payment methods. This is useful if you integrate services like Apple pay or Google pay.|No|\`false\`| +|\`payment\_description\`|A string used as the default description of a payment if none is available in cart.context.payment\_description.|No|-| + +*** + +## Setup Stripe Webhooks + +For production applications, you must set up webhooks in Stripe that inform Medusa of changes and updates to payments. Refer to [Stripe's documentation](https://docs.stripe.com/webhooks#add-a-webhook-endpoint) on how to setup webhooks. + +### Webhook URL + +Medusa has a `{server_url}/hooks/payment/{provider_id}` API route that you can use to register webhooks in Stripe, where: + +- `{server_url}` is the URL to your deployed Medusa application in server mode. +- `{provider_id}` is the ID of the provider, such as `stripe_stripe` for basic payments. + +The Stripe Module Provider supports the following payment types, and the webhook endpoint URL is different for each: + +|Stripe Payment Type|Webhook Endpoint URL| +|---|---|---| +|Basic Stripe Payment|\`\{server\_url}/hooks/payment/stripe\_stripe\`| +|Bancontact Payments|\`\{server\_url}/hooks/payment/stripe-bancontact\_stripe\`| +|BLIK Payments|\`\{server\_url}/hooks/payment/stripe-blik\_stripe\`| +|giropay Payments|\`\{server\_url}/hooks/payment/stripe-giropay\_stripe\`| +|iDEAL Payments|\`\{server\_url}/hooks/payment/stripe-ideal\_stripe\`| +|Przelewy24 Payments|\`\{server\_url}/hooks/payment/stripe-przelewy24\_stripe\`| +|PromptPay Payments|\`\{server\_url}/hooks/payment/stripe-promptpay\_stripe\`| + +### Webhook Events + +When you set up the webhook in Stripe, choose the following events to listen to: + +- `payment_intent.amount_capturable_updated` +- `payment_intent.succeeded` +- `payment_intent.payment_failed` + +*** + +## Useful Guides + +- [Storefront guide: Add Stripe payment method during checkout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/storefront-development/checkout/payment/stripe/index.html.md). +- [Integrate in Next.js Starter](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter#stripe-integration/index.html.md). +- [Customize Stripe Integration in Next.js Starter](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/guides/customize-stripe/index.html.md). + + +# Get Product Variant Prices using Query + +In this document, you'll learn how to retrieve product variant prices in the Medusa application using [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md). + +The Product Module doesn't provide pricing functionalities. The Medusa application links the Product Module's `ProductVariant` data model to the Pricing Module's `PriceSet` data model. + +So, to retrieve data across the linked records of the two modules, you use Query. + +## Retrieve All Product Variant Prices + +To retrieve all product variant prices, retrieve the product using Query and include among its fields `variants.prices.*`. + +For example: + +```ts highlights={[["6"]]} +const { data: products } = await query.graph({ + entity: "product", + fields: [ + "*", + "variants.*", + "variants.prices.*", + ], + filters: { + id: [ + "prod_123", + ], + }, +}) +``` + +Each variant in the retrieved products has a `prices` array property with all the product variant prices. Each price object has the properties of the [Pricing Module's Price data model](https://docs.medusajs.com/references/pricing/models/Price/index.html.md). + +*** + +## Retrieve Calculated Price for a Context + +The Pricing Module can calculate prices of a variant based on a [context](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md), such as the region ID or the currency code. + +Learn more about prices calculation in [this Pricing Module documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation/index.html.md). + +To retrieve calculated prices of variants based on a context, retrieve the products using Query and: + +- Pass `variants.calculated_price.*` in the `fields` property. +- Pass a `context` property in the object parameter. Its value is an object of objects that sets the context for the retrieved fields. + +For example: + +```ts highlights={[["10"], ["15"], ["16"], ["17"], ["18"], ["19"], ["20"], ["21"], ["22"]]} +import { QueryContext } from "@medusajs/framework/utils" + +// ... + +const { data: products } = await query.graph({ + entity: "product", + fields: [ + "*", + "variants.*", + "variants.calculated_price.*", + ], + filters: { + id: "prod_123", + }, + context: { + variants: { + calculated_price: QueryContext({ + region_id: "reg_01J3MRPDNXXXDSCC76Y6YCZARS", + currency_code: "eur", + }), + }, + }, +}) +``` + +For the context of the product variant's calculated price, you pass an object to `context` with the property `variants`, whose value is another object with the property `calculated_price`. + +`calculated_price`'s value is created using `QueryContext` from the Modules SDK, passing it a [calculation context object](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#calculation-context/index.html.md). + +Each variant in the retrieved products has a `calculated_price` object. Learn more about its properties in [this Pricing Module guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/pricing/price-calculation#returned-price-object/index.html.md). + + +# Calculate Product Variant Price with Taxes + +In this document, you'll learn how to calculate a product variant's price with taxes. + +## Step 0: Resolve Resources + +You'll need the following resources for the taxes calculation: + +1. [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) to retrieve the product's variants' prices for a context. Learn more about that in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/guides/price/index.html.md). +2. The Tax Module's main service to get the tax lines for each product. + +```ts +// other imports... +import { + Modules, + ContainerRegistrationKeys, +} from "@medusajs/framework/utils" + +// In an API route, workflow step, etc... +const query = container.resolve(ContainerRegistrationKeys.QUERY) +const taxModuleService = container.resolve( + Modules.TAX +) +``` + +*** + +## Step 1: Retrieve Prices for a Context + +After resolving the resources, use Query to retrieve the products with the variants' prices for a context: + +Learn more about retrieving product variants' prices for a context in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/guides/price/index.html.md). + +```ts +import { QueryContext } from "@medusajs/framework/utils" + +// ... + +const { data: products } = await query.graph({ + entity: "product", + fields: [ + "*", + "variants.*", + "variants.calculated_price.*", + ], + filters: { + id: "prod_123", + }, + context: { + variants: { + calculated_price: QueryContext({ + region_id: "region_123", + currency_code: "usd", + }), + }, + }, +}) +``` + +*** + +## Step 2: Get Tax Lines for Products + +To retrieve the tax line of each product, first, add the following utility method: + +```ts +// other imports... +import { + HttpTypes, + TaxableItemDTO, +} from "@medusajs/framework/types" + +// ... +const asTaxItem = (product: HttpTypes.StoreProduct): TaxableItemDTO[] => { + return product.variants + ?.map((variant) => { + if (!variant.calculated_price) { + return + } + + return { + id: variant.id, + product_id: product.id, + product_name: product.title, + product_categories: product.categories?.map((c) => c.name), + product_category_id: product.categories?.[0]?.id, + product_sku: variant.sku, + product_type: product.type, + product_type_id: product.type_id, + quantity: 1, + unit_price: variant.calculated_price.calculated_amount, + currency_code: variant.calculated_price.currency_code, + } + }) + .filter((v) => !!v) as unknown as TaxableItemDTO[] +} +``` + +This formats the products as items to calculate tax lines for. + +Then, use it when retrieving the tax lines of the products retrieved earlier: + +```ts +// other imports... +import { + ItemTaxLineDTO, +} from "@medusajs/framework/types" + +// ... +const taxLines = (await taxModuleService.getTaxLines( + products.map(asTaxItem).flat(), + { + // example of context properties. You can pass other ones. + address: { + country_code, + }, + } +)) as unknown as ItemTaxLineDTO[] +``` + +You use the Tax Module's main service's [getTaxLines method](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md) to retrieve the tax line. + +For the first parameter, you use the `asTaxItem` function to format the products as expected by the `getTaxLines` method. + +For the second parameter, you pass the current context. You can pass other details such as the customer's ID. + +Learn about the other context properties to pass in [the getTaxLines method's reference](https://docs.medusajs.com/references/tax/getTaxLines/index.html.md). + +*** + +## Step 3: Calculate Price with Tax for Variant + +To calculate the price with and without taxes for a variant, first, group the tax lines retrieved in the previous step by variant IDs: + +```ts highlights={taxLineHighlights} +const taxLinesMap = new Map() +taxLines.forEach((taxLine) => { + const variantId = taxLine.line_item_id + if (!taxLinesMap.has(variantId)) { + taxLinesMap.set(variantId, []) + } + + taxLinesMap.get(variantId)?.push(taxLine) +}) +``` + +Notice that the variant's ID is stored in the `line_item_id` property of a tax line since tax lines are used for line items in a cart. + +Then, loop over the products and their variants to retrieve the prices with and without taxes: + +```ts highlights={calculateTaxHighlights} +// other imports... +import { + calculateAmountsWithTax, +} from "@medusajs/framework/utils" + +// ... +products.forEach((product) => { + product.variants?.forEach((variant) => { + if (!variant.calculated_price) { + return + } + + const taxLinesForVariant = taxLinesMap.get(variant.id) || [] + const { priceWithTax, priceWithoutTax } = calculateAmountsWithTax({ + taxLines: taxLinesForVariant, + amount: variant.calculated_price!.calculated_amount!, + includesTax: + variant.calculated_price!.is_calculated_price_tax_inclusive!, + }) + + // do something with prices... + }) +}) +``` + +For each product variant, you: + +1. Retrieve its tax lines from the `taxLinesMap`. +2. Calculate its prices with and without taxes using the `calculateAmountsWithTax` from the Medusa Framework. +3. The `calculateAmountsWithTax` function returns an object having two properties: + - `priceWithTax`: The variant's price with the taxes applied. + - `priceWithoutTax`: The variant's price without taxes applied. + + +## Workflows + +- [createApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/createApiKeysWorkflow/index.html.md) +- [deleteApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteApiKeysWorkflow/index.html.md) +- [revokeApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/revokeApiKeysWorkflow/index.html.md) +- [updateApiKeysWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateApiKeysWorkflow/index.html.md) +- [linkSalesChannelsToApiKeyWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToApiKeyWorkflow/index.html.md) +- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/index.html.md) +- [createCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerGroupsWorkflow/index.html.md) +- [deleteCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerGroupsWorkflow/index.html.md) +- [linkCustomerGroupsToCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomerGroupsToCustomerWorkflow/index.html.md) +- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md) +- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md) +- [addShippingMethodToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addShippingMethodToCartWorkflow/index.html.md) +- [addToCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/addToCartWorkflow/index.html.md) +- [confirmVariantInventoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmVariantInventoryWorkflow/index.html.md) +- [completeCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeCartWorkflow/index.html.md) +- [createCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCartWorkflow/index.html.md) +- [createPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentCollectionForCartWorkflow/index.html.md) +- [listShippingOptionsForCartWithPricingWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWithPricingWorkflow/index.html.md) +- [listShippingOptionsForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/listShippingOptionsForCartWorkflow/index.html.md) +- [refreshCartItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartItemsWorkflow/index.html.md) - [refreshCartShippingMethodsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshCartShippingMethodsWorkflow/index.html.md) +- [refreshPaymentCollectionForCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshPaymentCollectionForCartWorkflow/index.html.md) +- [transferCartCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/transferCartCustomerWorkflow/index.html.md) +- [updateCartPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartPromotionsWorkflow/index.html.md) - [updateLineItemInCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLineItemInCartWorkflow/index.html.md) - [updateCartWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCartWorkflow/index.html.md) - [updateTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxLinesWorkflow/index.html.md) - [validateExistingPaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/validateExistingPaymentCollectionStep/index.html.md) -- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/index.html.md) -- [createLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLinksWorkflow/index.html.md) -- [dismissLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissLinksWorkflow/index.html.md) -- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/index.html.md) -- [generateResetPasswordTokenWorkflow](https://docs.medusajs.com/references/medusa-workflows/generateResetPasswordTokenWorkflow/index.html.md) -- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md) -- [deleteFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFilesWorkflow/index.html.md) -- [uploadFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/uploadFilesWorkflow/index.html.md) -- [createCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAddressesWorkflow/index.html.md) -- [createCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAccountWorkflow/index.html.md) - [createCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomersWorkflow/index.html.md) +- [createCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAccountWorkflow/index.html.md) +- [createCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerAddressesWorkflow/index.html.md) - [deleteCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerAddressesWorkflow/index.html.md) -- [updateCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerAddressesWorkflow/index.html.md) -- [updateCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomersWorkflow/index.html.md) - [deleteCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomersWorkflow/index.html.md) +- [updateCustomersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomersWorkflow/index.html.md) - [removeCustomerAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeCustomerAccountWorkflow/index.html.md) -- [createCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCustomerGroupsWorkflow/index.html.md) -- [deleteCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCustomerGroupsWorkflow/index.html.md) -- [updateCustomerGroupsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerGroupsWorkflow/index.html.md) -- [linkCustomerGroupsToCustomerWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomerGroupsToCustomerWorkflow/index.html.md) -- [linkCustomersToCustomerGroupWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkCustomersToCustomerGroupWorkflow/index.html.md) +- [updateCustomerAddressesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCustomerAddressesWorkflow/index.html.md) +- [createDefaultsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createDefaultsWorkflow/index.html.md) +- [deleteFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFilesWorkflow/index.html.md) +- [uploadFilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/uploadFilesWorkflow/index.html.md) +- [cancelFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelFulfillmentWorkflow/index.html.md) - [batchShippingOptionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchShippingOptionRulesWorkflow/index.html.md) +- [calculateShippingOptionsPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/calculateShippingOptionsPricesWorkflow/index.html.md) - [createFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentWorkflow/index.html.md) - [createReturnFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnFulfillmentWorkflow/index.html.md) -- [cancelFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelFulfillmentWorkflow/index.html.md) -- [calculateShippingOptionsPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/calculateShippingOptionsPricesWorkflow/index.html.md) - [createServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createServiceZonesWorkflow/index.html.md) - [createShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingOptionsWorkflow/index.html.md) - [createShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShippingProfilesWorkflow/index.html.md) - [createShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createShipmentWorkflow/index.html.md) - [deleteFulfillmentSetsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteFulfillmentSetsWorkflow/index.html.md) - [deleteServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteServiceZonesWorkflow/index.html.md) -- [updateFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateFulfillmentWorkflow/index.html.md) - [deleteShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingOptionsWorkflow/index.html.md) -- [markFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markFulfillmentAsDeliveredWorkflow/index.html.md) - [updateServiceZonesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateServiceZonesWorkflow/index.html.md) +- [updateFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateFulfillmentWorkflow/index.html.md) +- [markFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markFulfillmentAsDeliveredWorkflow/index.html.md) - [updateShippingOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingOptionsWorkflow/index.html.md) - [updateShippingProfilesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateShippingProfilesWorkflow/index.html.md) - [validateFulfillmentDeliverabilityStep](https://docs.medusajs.com/references/medusa-workflows/validateFulfillmentDeliverabilityStep/index.html.md) - [acceptInviteWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptInviteWorkflow/index.html.md) -- [createInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInvitesWorkflow/index.html.md) - [deleteInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInvitesWorkflow/index.html.md) +- [createInvitesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInvitesWorkflow/index.html.md) - [refreshInviteTokensWorkflow](https://docs.medusajs.com/references/medusa-workflows/refreshInviteTokensWorkflow/index.html.md) -- [batchInventoryItemLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchInventoryItemLevelsWorkflow/index.html.md) - [bulkCreateDeleteLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/bulkCreateDeleteLevelsWorkflow/index.html.md) +- [batchInventoryItemLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchInventoryItemLevelsWorkflow/index.html.md) - [createInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryItemsWorkflow/index.html.md) -- [deleteInventoryItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryItemWorkflow/index.html.md) - [createInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createInventoryLevelsWorkflow/index.html.md) +- [deleteInventoryItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryItemWorkflow/index.html.md) - [deleteInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteInventoryLevelsWorkflow/index.html.md) +- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/index.html.md) - [updateInventoryItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryItemsWorkflow/index.html.md) - [validateInventoryLevelsDelete](https://docs.medusajs.com/references/medusa-workflows/validateInventoryLevelsDelete/index.html.md) -- [updateInventoryLevelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateInventoryLevelsWorkflow/index.html.md) - [deleteLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteLineItemsWorkflow/index.html.md) -- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/index.html.md) +- [batchLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinksWorkflow/index.html.md) +- [dismissLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissLinksWorkflow/index.html.md) +- [createLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLinksWorkflow/index.html.md) +- [updateLinksWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateLinksWorkflow/index.html.md) +- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/index.html.md) +- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/index.html.md) +- [createPaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentSessionsWorkflow/index.html.md) +- [deleteRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRefundReasonsWorkflow/index.html.md) +- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md) - [capturePaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/capturePaymentWorkflow/index.html.md) -- [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) -- [validateRefundStep](https://docs.medusajs.com/references/medusa-workflows/validateRefundStep/index.html.md) +- [processPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/processPaymentWorkflow/index.html.md) - [refundPaymentsWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentsWorkflow/index.html.md) - [validatePaymentsRefundStep](https://docs.medusajs.com/references/medusa-workflows/validatePaymentsRefundStep/index.html.md) -- [batchPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPriceListPricesWorkflow/index.html.md) -- [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md) -- [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/index.html.md) -- [createPriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListsWorkflow/index.html.md) -- [createPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListPricesWorkflow/index.html.md) -- [updatePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListPricesWorkflow/index.html.md) -- [updatePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListsWorkflow/index.html.md) +- [validateRefundStep](https://docs.medusajs.com/references/medusa-workflows/validateRefundStep/index.html.md) +- [refundPaymentWorkflow](https://docs.medusajs.com/references/medusa-workflows/refundPaymentWorkflow/index.html.md) - [deletePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePricePreferencesWorkflow/index.html.md) -- [updatePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePricePreferencesWorkflow/index.html.md) - [createPricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPricePreferencesWorkflow/index.html.md) -- [addOrderLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrderLineItemsWorkflow/index.html.md) -- [archiveOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/archiveOrderWorkflow/index.html.md) -- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/index.html.md) -- [beginClaimOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderWorkflow/index.html.md) +- [updatePricePreferencesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePricePreferencesWorkflow/index.html.md) - [acceptOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferValidationStep/index.html.md) - [acceptOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/acceptOrderTransferWorkflow/index.html.md) +- [beginClaimOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderValidationStep/index.html.md) +- [archiveOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/archiveOrderWorkflow/index.html.md) +- [beginClaimOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginClaimOrderWorkflow/index.html.md) +- [addOrderLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrderLineItemsWorkflow/index.html.md) - [beginExchangeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginExchangeOrderWorkflow/index.html.md) -- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md) -- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md) - [beginOrderEditOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditOrderWorkflow/index.html.md) +- [beginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderEditValidationStep/index.html.md) - [beginReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnValidationStep/index.html.md) - [beginReturnOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderValidationStep/index.html.md) - [beginReceiveReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReceiveReturnWorkflow/index.html.md) +- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md) +- [beginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/beginOrderExchangeValidationStep/index.html.md) +- [cancelBeginOrderClaimValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimValidationStep/index.html.md) - [cancelBeginOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimWorkflow/index.html.md) - [cancelBeginOrderEditValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditValidationStep/index.html.md) -- [cancelBeginOrderClaimValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderClaimValidationStep/index.html.md) -- [cancelBeginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeValidationStep/index.html.md) -- [beginReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/beginReturnOrderWorkflow/index.html.md) - [cancelBeginOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeWorkflow/index.html.md) - [cancelClaimValidateOrderStep](https://docs.medusajs.com/references/medusa-workflows/cancelClaimValidateOrderStep/index.html.md) +- [cancelBeginOrderExchangeValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderExchangeValidationStep/index.html.md) - [cancelExchangeValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelExchangeValidateOrder/index.html.md) -- [cancelBeginOrderEditWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditWorkflow/index.html.md) - [cancelOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderChangeWorkflow/index.html.md) - [cancelOrderExchangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderExchangeWorkflow/index.html.md) - [cancelOrderClaimWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderClaimWorkflow/index.html.md) - [cancelOrderFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentValidateOrder/index.html.md) +- [cancelOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentWorkflow/index.html.md) - [cancelOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderTransferRequestWorkflow/index.html.md) -- [cancelOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderWorkflow/index.html.md) -- [cancelReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnReceiveWorkflow/index.html.md) +- [cancelBeginOrderEditWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelBeginOrderEditWorkflow/index.html.md) - [cancelReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelReceiveReturnValidationStep/index.html.md) - [cancelRequestReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelRequestReturnValidationStep/index.html.md) -- [cancelOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderFulfillmentWorkflow/index.html.md) +- [cancelOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelOrderWorkflow/index.html.md) +- [cancelReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnReceiveWorkflow/index.html.md) - [cancelReturnValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelReturnValidateOrder/index.html.md) - [cancelReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnRequestWorkflow/index.html.md) - [cancelTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/cancelTransferOrderRequestValidationStep/index.html.md) -- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md) - [cancelReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/cancelReturnWorkflow/index.html.md) +- [cancelValidateOrder](https://docs.medusajs.com/references/medusa-workflows/cancelValidateOrder/index.html.md) - [completeOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/completeOrderWorkflow/index.html.md) - [confirmClaimRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestValidationStep/index.html.md) -- [confirmOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestValidationStep/index.html.md) -- [confirmOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestWorkflow/index.html.md) - [confirmClaimRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmClaimRequestWorkflow/index.html.md) -- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md) - [confirmExchangeRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestWorkflow/index.html.md) - [confirmExchangeRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmExchangeRequestValidationStep/index.html.md) +- [confirmOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestValidationStep/index.html.md) +- [confirmOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmOrderEditRequestWorkflow/index.html.md) +- [confirmReceiveReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReceiveReturnValidationStep/index.html.md) - [confirmReturnReceiveWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnReceiveWorkflow/index.html.md) - [confirmReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestValidationStep/index.html.md) -- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/index.html.md) -- [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md) -- [confirmReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestWorkflow/index.html.md) - [createAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createAndCompleteReturnOrderWorkflow/index.html.md) +- [confirmReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/confirmReturnRequestWorkflow/index.html.md) +- [createClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodValidationStep/index.html.md) - [createCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/createCompleteReturnValidationStep/index.html.md) +- [createClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createClaimShippingMethodWorkflow/index.html.md) - [createExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodValidationStep/index.html.md) -- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md) -- [createFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentValidateOrder/index.html.md) - [createExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createExchangeShippingMethodWorkflow/index.html.md) +- [createFulfillmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createFulfillmentValidateOrder/index.html.md) +- [createOrUpdateOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrUpdateOrderPaymentCollectionWorkflow/index.html.md) +- [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md) - [createOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeWorkflow/index.html.md) - [createOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodValidationStep/index.html.md) - [createOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderEditShippingMethodWorkflow/index.html.md) - [createOrderFulfillmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderFulfillmentWorkflow/index.html.md) - [createOrderPaymentCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderPaymentCollectionWorkflow/index.html.md) -- [createOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderChangeActionsWorkflow/index.html.md) -- [createOrderShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderShipmentWorkflow/index.html.md) - [createOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderWorkflow/index.html.md) - [createOrdersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrdersWorkflow/index.html.md) +- [createOrderShipmentWorkflow](https://docs.medusajs.com/references/medusa-workflows/createOrderShipmentWorkflow/index.html.md) - [createReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodValidationStep/index.html.md) - [createReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnShippingMethodWorkflow/index.html.md) - [createShipmentValidateOrder](https://docs.medusajs.com/references/medusa-workflows/createShipmentValidateOrder/index.html.md) - [declineOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderChangeWorkflow/index.html.md) - [declineOrderTransferRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/declineOrderTransferRequestWorkflow/index.html.md) - [declineTransferOrderRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/declineTransferOrderRequestValidationStep/index.html.md) +- [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md) - [deleteOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeActionsWorkflow/index.html.md) - [deleteOrderChangeWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteOrderChangeWorkflow/index.html.md) -- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/index.html.md) - [dismissItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestValidationStep/index.html.md) -- [deleteOrderPaymentCollections](https://docs.medusajs.com/references/medusa-workflows/deleteOrderPaymentCollections/index.html.md) - [dismissItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/dismissItemReturnRequestWorkflow/index.html.md) -- [markOrderFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markOrderFulfillmentAsDeliveredWorkflow/index.html.md) -- [exchangeRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeRequestItemReturnValidationStep/index.html.md) +- [exchangeAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeAddNewItemValidationStep/index.html.md) - [getOrderDetailWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrderDetailWorkflow/index.html.md) +- [exchangeRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/exchangeRequestItemReturnValidationStep/index.html.md) - [getOrdersListWorkflow](https://docs.medusajs.com/references/medusa-workflows/getOrdersListWorkflow/index.html.md) -- [markPaymentCollectionAsPaid](https://docs.medusajs.com/references/medusa-workflows/markPaymentCollectionAsPaid/index.html.md) +- [markOrderFulfillmentAsDeliveredWorkflow](https://docs.medusajs.com/references/medusa-workflows/markOrderFulfillmentAsDeliveredWorkflow/index.html.md) - [orderClaimAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemValidationStep/index.html.md) +- [markPaymentCollectionAsPaid](https://docs.medusajs.com/references/medusa-workflows/markPaymentCollectionAsPaid/index.html.md) - [orderClaimAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimAddNewItemWorkflow/index.html.md) - [orderClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemValidationStep/index.html.md) - [orderClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimItemWorkflow/index.html.md) - [orderClaimRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnValidationStep/index.html.md) - [orderClaimRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderClaimRequestItemReturnWorkflow/index.html.md) - [orderEditAddNewItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemValidationStep/index.html.md) -- [orderEditUpdateItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityWorkflow/index.html.md) -- [orderEditUpdateItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityValidationStep/index.html.md) - [orderEditAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditAddNewItemWorkflow/index.html.md) +- [orderEditUpdateItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityValidationStep/index.html.md) - [orderExchangeAddNewItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeAddNewItemWorkflow/index.html.md) +- [orderEditUpdateItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderEditUpdateItemQuantityWorkflow/index.html.md) - [orderExchangeRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/orderExchangeRequestItemReturnWorkflow/index.html.md) - [orderFulfillmentDeliverablilityValidationStep](https://docs.medusajs.com/references/medusa-workflows/orderFulfillmentDeliverablilityValidationStep/index.html.md) +- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md) - [receiveAndCompleteReturnOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveAndCompleteReturnOrderWorkflow/index.html.md) - [receiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestValidationStep/index.html.md) -- [receiveCompleteReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/receiveCompleteReturnValidationStep/index.html.md) -- [removeClaimAddItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimAddItemActionValidationStep/index.html.md) - [receiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/receiveItemReturnRequestWorkflow/index.html.md) +- [removeClaimAddItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimAddItemActionValidationStep/index.html.md) +- [removeAddItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeAddItemClaimActionWorkflow/index.html.md) - [removeClaimItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimItemActionValidationStep/index.html.md) - [removeClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodValidationStep/index.html.md) -- [removeAddItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeAddItemClaimActionWorkflow/index.html.md) - [removeClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeClaimShippingMethodWorkflow/index.html.md) -- [removeItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemClaimActionWorkflow/index.html.md) - [removeExchangeItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeItemActionValidationStep/index.html.md) -- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md) - [removeExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodValidationStep/index.html.md) +- [removeExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeExchangeShippingMethodWorkflow/index.html.md) +- [removeItemClaimActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemClaimActionWorkflow/index.html.md) - [removeItemExchangeActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemExchangeActionWorkflow/index.html.md) - [removeItemReceiveReturnActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionValidationStep/index.html.md) +- [removeItemOrderEditActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemOrderEditActionWorkflow/index.html.md) - [removeItemReceiveReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReceiveReturnActionWorkflow/index.html.md) - [removeItemReturnActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemReturnActionWorkflow/index.html.md) - [removeOrderEditItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditItemActionValidationStep/index.html.md) -- [removeItemOrderEditActionWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeItemOrderEditActionWorkflow/index.html.md) +- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/index.html.md) - [removeOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodValidationStep/index.html.md) - [removeOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeOrderEditShippingMethodWorkflow/index.html.md) -- [removeReturnItemActionValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnItemActionValidationStep/index.html.md) - [removeReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodValidationStep/index.html.md) - [removeReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeReturnShippingMethodWorkflow/index.html.md) +- [requestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnValidationStep/index.html.md) - [requestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnWorkflow/index.html.md) - [requestOrderEditRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestValidationStep/index.html.md) -- [requestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestItemReturnValidationStep/index.html.md) - [requestOrderEditRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderEditRequestWorkflow/index.html.md) +- [requestOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferValidationStep/index.html.md) - [requestOrderTransferWorkflow](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferWorkflow/index.html.md) - [throwUnlessPaymentCollectionNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessPaymentCollectionNotPaid/index.html.md) -- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/index.html.md) - [throwUnlessStatusIsNotPaid](https://docs.medusajs.com/references/medusa-workflows/throwUnlessStatusIsNotPaid/index.html.md) -- [requestOrderTransferValidationStep](https://docs.medusajs.com/references/medusa-workflows/requestOrderTransferValidationStep/index.html.md) -- [updateClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodValidationStep/index.html.md) +- [updateClaimAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemValidationStep/index.html.md) - [updateClaimAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimAddItemWorkflow/index.html.md) -- [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md) - [updateClaimItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemValidationStep/index.html.md) - [updateClaimItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimItemWorkflow/index.html.md) +- [updateClaimShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodValidationStep/index.html.md) +- [updateClaimShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateClaimShippingMethodWorkflow/index.html.md) - [updateExchangeAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemValidationStep/index.html.md) -- [updateExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodWorkflow/index.html.md) - [updateExchangeAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeAddItemWorkflow/index.html.md) - [updateExchangeShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodValidationStep/index.html.md) +- [updateExchangeShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateExchangeShippingMethodWorkflow/index.html.md) - [updateOrderChangeActionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangeActionsWorkflow/index.html.md) -- [updateOrderEditAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemValidationStep/index.html.md) - [updateOrderChangesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderChangesWorkflow/index.html.md) +- [updateOrderEditAddItemValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemValidationStep/index.html.md) - [updateOrderEditAddItemWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditAddItemWorkflow/index.html.md) - [updateOrderEditItemQuantityValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityValidationStep/index.html.md) - [updateOrderEditItemQuantityWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditItemQuantityWorkflow/index.html.md) -- [updateOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodWorkflow/index.html.md) - [updateOrderEditShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodValidationStep/index.html.md) +- [updateOrderEditShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderEditShippingMethodWorkflow/index.html.md) +- [updateOrderTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderTaxLinesWorkflow/index.html.md) - [updateOrderValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateOrderValidationStep/index.html.md) - [updateReceiveItemReturnRequestValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestValidationStep/index.html.md) -- [updateOrderTaxLinesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderTaxLinesWorkflow/index.html.md) - [updateOrderWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateOrderWorkflow/index.html.md) - [updateReceiveItemReturnRequestWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReceiveItemReturnRequestWorkflow/index.html.md) - [updateRequestItemReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnValidationStep/index.html.md) -- [updateReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodWorkflow/index.html.md) -- [updateReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodValidationStep/index.html.md) - [updateRequestItemReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRequestItemReturnWorkflow/index.html.md) +- [updateReturnShippingMethodValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodValidationStep/index.html.md) - [updateReturnValidationStep](https://docs.medusajs.com/references/medusa-workflows/updateReturnValidationStep/index.html.md) +- [updateReturnShippingMethodWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnShippingMethodWorkflow/index.html.md) - [updateReturnWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnWorkflow/index.html.md) -- [batchPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPromotionRulesWorkflow/index.html.md) -- [addOrRemoveCampaignPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrRemoveCampaignPromotionsWorkflow/index.html.md) -- [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md) -- [createPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionRulesWorkflow/index.html.md) -- [deletePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionRulesWorkflow/index.html.md) -- [createPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionsWorkflow/index.html.md) -- [deleteCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCampaignsWorkflow/index.html.md) -- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/index.html.md) -- [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md) -- [updatePromotionsStatusWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsStatusWorkflow/index.html.md) -- [updatePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionRulesWorkflow/index.html.md) -- [updatePromotionsValidationStep](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsValidationStep/index.html.md) -- [updatePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsWorkflow/index.html.md) -- [batchLinkProductsToCategoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCategoryWorkflow/index.html.md) - [batchLinkProductsToCollectionWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCollectionWorkflow/index.html.md) -- [createCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCollectionsWorkflow/index.html.md) -- [batchProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductsWorkflow/index.html.md) +- [batchLinkProductsToCategoryWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchLinkProductsToCategoryWorkflow/index.html.md) - [batchProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductVariantsWorkflow/index.html.md) +- [batchProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchProductsWorkflow/index.html.md) +- [createCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCollectionsWorkflow/index.html.md) - [createProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductOptionsWorkflow/index.html.md) - [createProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTagsWorkflow/index.html.md) - [createProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductTypesWorkflow/index.html.md) +- [createProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductsWorkflow/index.html.md) - [createProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductVariantsWorkflow/index.html.md) - [deleteProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductOptionsWorkflow/index.html.md) - [deleteCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCollectionsWorkflow/index.html.md) -- [createProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductsWorkflow/index.html.md) -- [deleteProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductVariantsWorkflow/index.html.md) - [deleteProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTypesWorkflow/index.html.md) - [deleteProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductTagsWorkflow/index.html.md) -- [exportProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/exportProductsWorkflow/index.html.md) +- [deleteProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductVariantsWorkflow/index.html.md) - [deleteProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductsWorkflow/index.html.md) - [importProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/importProductsWorkflow/index.html.md) +- [exportProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/exportProductsWorkflow/index.html.md) - [updateCollectionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCollectionsWorkflow/index.html.md) -- [updateProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTagsWorkflow/index.html.md) - [updateProductOptionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductOptionsWorkflow/index.html.md) -- [updateProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductVariantsWorkflow/index.html.md) -- [updateProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductsWorkflow/index.html.md) +- [updateProductTagsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTagsWorkflow/index.html.md) - [updateProductTypesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductTypesWorkflow/index.html.md) +- [updateProductsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductsWorkflow/index.html.md) +- [updateProductVariantsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductVariantsWorkflow/index.html.md) - [upsertVariantPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/upsertVariantPricesWorkflow/index.html.md) - [validateProductInputStep](https://docs.medusajs.com/references/medusa-workflows/validateProductInputStep/index.html.md) -- [createProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductCategoriesWorkflow/index.html.md) -- [deleteProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductCategoriesWorkflow/index.html.md) -- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/index.html.md) +- [addOrRemoveCampaignPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/addOrRemoveCampaignPromotionsWorkflow/index.html.md) +- [batchPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPromotionRulesWorkflow/index.html.md) +- [createCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createCampaignsWorkflow/index.html.md) +- [createPromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionRulesWorkflow/index.html.md) +- [createPromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPromotionsWorkflow/index.html.md) +- [deleteCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteCampaignsWorkflow/index.html.md) +- [deletePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionRulesWorkflow/index.html.md) +- [deletePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePromotionsWorkflow/index.html.md) +- [updateCampaignsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateCampaignsWorkflow/index.html.md) +- [updatePromotionRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionRulesWorkflow/index.html.md) +- [updatePromotionsStatusWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsStatusWorkflow/index.html.md) +- [updatePromotionsValidationStep](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsValidationStep/index.html.md) +- [updatePromotionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePromotionsWorkflow/index.html.md) - [createReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReservationsWorkflow/index.html.md) -- [deleteReservationsByLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsByLineItemsWorkflow/index.html.md) - [deleteReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsWorkflow/index.html.md) +- [deleteReservationsByLineItemsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReservationsByLineItemsWorkflow/index.html.md) - [updateReservationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReservationsWorkflow/index.html.md) -- [createReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnReasonsWorkflow/index.html.md) -- [updateReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnReasonsWorkflow/index.html.md) -- [deleteReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReturnReasonsWorkflow/index.html.md) -- [updateRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRegionsWorkflow/index.html.md) +- [batchPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/batchPriceListPricesWorkflow/index.html.md) +- [createPriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListPricesWorkflow/index.html.md) +- [createPriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPriceListsWorkflow/index.html.md) +- [deletePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePriceListsWorkflow/index.html.md) +- [removePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/removePriceListPricesWorkflow/index.html.md) +- [updatePriceListPricesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListPricesWorkflow/index.html.md) +- [updatePriceListsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updatePriceListsWorkflow/index.html.md) - [createRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRegionsWorkflow/index.html.md) - [deleteRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRegionsWorkflow/index.html.md) -- [createPaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createPaymentSessionsWorkflow/index.html.md) -- [createRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createRefundReasonsWorkflow/index.html.md) -- [deletePaymentSessionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deletePaymentSessionsWorkflow/index.html.md) -- [deleteRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteRefundReasonsWorkflow/index.html.md) -- [updateRefundReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRefundReasonsWorkflow/index.html.md) -- [deleteSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteSalesChannelsWorkflow/index.html.md) -- [linkProductsToSalesChannelWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkProductsToSalesChannelWorkflow/index.html.md) -- [updateSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateSalesChannelsWorkflow/index.html.md) +- [updateRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateRegionsWorkflow/index.html.md) +- [createReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createReturnReasonsWorkflow/index.html.md) +- [deleteReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteReturnReasonsWorkflow/index.html.md) +- [updateReturnReasonsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateReturnReasonsWorkflow/index.html.md) - [createSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createSalesChannelsWorkflow/index.html.md) +- [updateSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateSalesChannelsWorkflow/index.html.md) +- [linkProductsToSalesChannelWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkProductsToSalesChannelWorkflow/index.html.md) +- [deleteSalesChannelsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteSalesChannelsWorkflow/index.html.md) - [deleteShippingProfileWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteShippingProfileWorkflow/index.html.md) - [validateStepShippingProfileDelete](https://docs.medusajs.com/references/medusa-workflows/validateStepShippingProfileDelete/index.html.md) +- [createLocationFulfillmentSetWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLocationFulfillmentSetWorkflow/index.html.md) +- [createStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStockLocationsWorkflow/index.html.md) +- [deleteStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStockLocationsWorkflow/index.html.md) +- [linkSalesChannelsToStockLocationWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToStockLocationWorkflow/index.html.md) +- [updateStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStockLocationsWorkflow/index.html.md) +- [createTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRateRulesWorkflow/index.html.md) +- [deleteTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRateRulesWorkflow/index.html.md) +- [createTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRatesWorkflow/index.html.md) +- [createTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRegionsWorkflow/index.html.md) +- [setTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/setTaxRateRulesWorkflow/index.html.md) +- [deleteTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRegionsWorkflow/index.html.md) +- [deleteTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRatesWorkflow/index.html.md) +- [maybeListTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/maybeListTaxRateRuleIdsStep/index.html.md) +- [updateTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRatesWorkflow/index.html.md) +- [updateTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRegionsWorkflow/index.html.md) - [createStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStoresWorkflow/index.html.md) - [deleteStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStoresWorkflow/index.html.md) - [updateStoresWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStoresWorkflow/index.html.md) -- [createLocationFulfillmentSetWorkflow](https://docs.medusajs.com/references/medusa-workflows/createLocationFulfillmentSetWorkflow/index.html.md) -- [createStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createStockLocationsWorkflow/index.html.md) -- [deleteStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteStockLocationsWorkflow/index.html.md) -- [linkSalesChannelsToStockLocationWorkflow](https://docs.medusajs.com/references/medusa-workflows/linkSalesChannelsToStockLocationWorkflow/index.html.md) -- [updateStockLocationsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateStockLocationsWorkflow/index.html.md) - [createUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUserAccountWorkflow/index.html.md) - [createUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/createUsersWorkflow/index.html.md) - [deleteUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteUsersWorkflow/index.html.md) - [removeUserAccountWorkflow](https://docs.medusajs.com/references/medusa-workflows/removeUserAccountWorkflow/index.html.md) - [updateUsersWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateUsersWorkflow/index.html.md) -- [createTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRateRulesWorkflow/index.html.md) -- [createTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRatesWorkflow/index.html.md) -- [deleteTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRatesWorkflow/index.html.md) -- [deleteTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRateRulesWorkflow/index.html.md) -- [createTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/createTaxRegionsWorkflow/index.html.md) -- [deleteTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteTaxRegionsWorkflow/index.html.md) -- [updateTaxRatesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRatesWorkflow/index.html.md) -- [setTaxRateRulesWorkflow](https://docs.medusajs.com/references/medusa-workflows/setTaxRateRulesWorkflow/index.html.md) -- [maybeListTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/maybeListTaxRateRuleIdsStep/index.html.md) -- [updateTaxRegionsWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateTaxRegionsWorkflow/index.html.md) +- [createProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/createProductCategoriesWorkflow/index.html.md) +- [deleteProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/deleteProductCategoriesWorkflow/index.html.md) +- [updateProductCategoriesWorkflow](https://docs.medusajs.com/references/medusa-workflows/updateProductCategoriesWorkflow/index.html.md) ## Steps -- [createApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/createApiKeysStep/index.html.md) -- [deleteApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteApiKeysStep/index.html.md) -- [linkSalesChannelsToApiKeyStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkSalesChannelsToApiKeyStep/index.html.md) -- [revokeApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/revokeApiKeysStep/index.html.md) -- [updateApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateApiKeysStep/index.html.md) -- [validateSalesChannelsExistStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateSalesChannelsExistStep/index.html.md) -- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/index.html.md) -- [dismissRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/dismissRemoteLinkStep/index.html.md) -- [createRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRemoteLinkStep/index.html.md) -- [removeRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRemoteLinkStep/index.html.md) -- [emitEventStep](https://docs.medusajs.com/references/medusa-workflows/steps/emitEventStep/index.html.md) -- [updateRemoteLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRemoteLinksStep/index.html.md) -- [useQueryGraphStep](https://docs.medusajs.com/references/medusa-workflows/steps/useQueryGraphStep/index.html.md) -- [useRemoteQueryStep](https://docs.medusajs.com/references/medusa-workflows/steps/useRemoteQueryStep/index.html.md) -- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md) -- [createCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerAddressesStep/index.html.md) -- [createCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomersStep/index.html.md) -- [deleteCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerAddressesStep/index.html.md) -- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md) -- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md) -- [deleteCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomersStep/index.html.md) -- [maybeUnsetDefaultShippingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultShippingAddressesStep/index.html.md) -- [updateCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomersStep/index.html.md) -- [validateCustomerAccountCreation](https://docs.medusajs.com/references/medusa-workflows/steps/validateCustomerAccountCreation/index.html.md) -- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/index.html.md) - [addShippingMethodToCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/addShippingMethodToCartStep/index.html.md) +- [confirmInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/confirmInventoryStep/index.html.md) - [createCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCartsStep/index.html.md) - [createLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemAdjustmentsStep/index.html.md) - [createLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createLineItemsStep/index.html.md) -- [findOneOrAnyRegionStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOneOrAnyRegionStep/index.html.md) - [createPaymentCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentCollectionsStep/index.html.md) - [createShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingMethodAdjustmentsStep/index.html.md) -- [getLineItemActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getLineItemActionsStep/index.html.md) +- [findOneOrAnyRegionStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOneOrAnyRegionStep/index.html.md) - [findOrCreateCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/findOrCreateCustomerStep/index.html.md) - [findSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/findSalesChannelStep/index.html.md) - [getActionsToComputeFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getActionsToComputeFromPromotionsStep/index.html.md) +- [getLineItemActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getLineItemActionsStep/index.html.md) - [getPromotionCodesToApply](https://docs.medusajs.com/references/medusa-workflows/steps/getPromotionCodesToApply/index.html.md) - [getVariantPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantPriceSetsStep/index.html.md) -- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/index.html.md) -- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/index.html.md) - [getVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantsStep/index.html.md) +- [prepareAdjustmentsFromPromotionActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/prepareAdjustmentsFromPromotionActionsStep/index.html.md) +- [removeLineItemAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeLineItemAdjustmentsStep/index.html.md) - [removeShippingMethodAdjustmentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodAdjustmentsStep/index.html.md) -- [removeShippingMethodFromCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodFromCartStep/index.html.md) - [reserveInventoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/reserveInventoryStep/index.html.md) +- [removeShippingMethodFromCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeShippingMethodFromCartStep/index.html.md) - [retrieveCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/retrieveCartStep/index.html.md) - [setTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setTaxLinesForItemsStep/index.html.md) -- [updateLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStep/index.html.md) - [updateCartsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartsStep/index.html.md) - [updateCartPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCartPromotionsStep/index.html.md) +- [updateLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStep/index.html.md) - [updateShippingMethodsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingMethodsStep/index.html.md) +- [validateCartPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartPaymentsStep/index.html.md) - [validateAndReturnShippingMethodsDataStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateAndReturnShippingMethodsDataStep/index.html.md) - [validateCartShippingOptionsPriceStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsPriceStep/index.html.md) -- [validateCartPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartPaymentsStep/index.html.md) - [validateCartShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartShippingOptionsStep/index.html.md) -- [validateCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartStep/index.html.md) -- [validateShippingStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingStep/index.html.md) - [validateLineItemPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateLineItemPricesStep/index.html.md) +- [validateShippingStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingStep/index.html.md) +- [validateCartStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateCartStep/index.html.md) - [validateVariantPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPricesStep/index.html.md) +- [createApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/createApiKeysStep/index.html.md) +- [deleteApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteApiKeysStep/index.html.md) +- [linkSalesChannelsToApiKeyStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkSalesChannelsToApiKeyStep/index.html.md) +- [revokeApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/revokeApiKeysStep/index.html.md) +- [updateApiKeysStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateApiKeysStep/index.html.md) +- [validateSalesChannelsExistStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateSalesChannelsExistStep/index.html.md) +- [setAuthAppMetadataStep](https://docs.medusajs.com/references/medusa-workflows/steps/setAuthAppMetadataStep/index.html.md) +- [createRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRemoteLinkStep/index.html.md) +- [dismissRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/dismissRemoteLinkStep/index.html.md) +- [emitEventStep](https://docs.medusajs.com/references/medusa-workflows/steps/emitEventStep/index.html.md) +- [removeRemoteLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRemoteLinkStep/index.html.md) +- [updateRemoteLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRemoteLinksStep/index.html.md) +- [useQueryGraphStep](https://docs.medusajs.com/references/medusa-workflows/steps/useQueryGraphStep/index.html.md) +- [useRemoteQueryStep](https://docs.medusajs.com/references/medusa-workflows/steps/useRemoteQueryStep/index.html.md) +- [validatePresenceOfStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePresenceOfStep/index.html.md) - [createCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerGroupsStep/index.html.md) -- [linkCustomerGroupsToCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomerGroupsToCustomerStep/index.html.md) - [deleteCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerGroupStep/index.html.md) - [linkCustomersToCustomerGroupStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomersToCustomerGroupStep/index.html.md) +- [linkCustomerGroupsToCustomerStep](https://docs.medusajs.com/references/medusa-workflows/steps/linkCustomerGroupsToCustomerStep/index.html.md) - [updateCustomerGroupsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerGroupsStep/index.html.md) +- [createCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomerAddressesStep/index.html.md) +- [createCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCustomersStep/index.html.md) +- [deleteCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomerAddressesStep/index.html.md) +- [deleteCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCustomersStep/index.html.md) +- [maybeUnsetDefaultBillingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultBillingAddressesStep/index.html.md) +- [maybeUnsetDefaultShippingAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/maybeUnsetDefaultShippingAddressesStep/index.html.md) +- [updateCustomerAddressesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomerAddressesStep/index.html.md) +- [validateCustomerAccountCreation](https://docs.medusajs.com/references/medusa-workflows/steps/validateCustomerAccountCreation/index.html.md) +- [updateCustomersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCustomersStep/index.html.md) - [createDefaultStoreStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultStoreStep/index.html.md) -- [deleteFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFilesStep/index.html.md) -- [uploadFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/uploadFilesStep/index.html.md) - [buildPriceSet](https://docs.medusajs.com/references/medusa-workflows/steps/buildPriceSet/index.html.md) - [calculateShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/calculateShippingOptionsPricesStep/index.html.md) - [cancelFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelFulfillmentStep/index.html.md) -- [createFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentStep/index.html.md) - [createFulfillmentSets](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentSets/index.html.md) -- [createShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionRulesStep/index.html.md) +- [createFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createFulfillmentStep/index.html.md) - [createServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createServiceZonesStep/index.html.md) -- [createShippingOptionsPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionsPriceSetsStep/index.html.md) - [createReturnFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnFulfillmentStep/index.html.md) +- [createShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionRulesStep/index.html.md) - [createShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingProfilesStep/index.html.md) - [deleteFulfillmentSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFulfillmentSetsStep/index.html.md) +- [createShippingOptionsPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createShippingOptionsPriceSetsStep/index.html.md) - [deleteServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteServiceZonesStep/index.html.md) - [deleteShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionRulesStep/index.html.md) - [deleteShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingOptionsStep/index.html.md) -- [updateFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateFulfillmentStep/index.html.md) - [setShippingOptionsPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/setShippingOptionsPricesStep/index.html.md) +- [updateFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateFulfillmentStep/index.html.md) - [updateServiceZonesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateServiceZonesStep/index.html.md) - [updateShippingOptionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingOptionRulesStep/index.html.md) - [upsertShippingOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/upsertShippingOptionsStep/index.html.md) - [updateShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateShippingProfilesStep/index.html.md) - [validateShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShipmentStep/index.html.md) - [validateShippingOptionPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateShippingOptionPricesStep/index.html.md) +- [createInviteStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInviteStep/index.html.md) +- [deleteInvitesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInvitesStep/index.html.md) +- [refreshInviteTokensStep](https://docs.medusajs.com/references/medusa-workflows/steps/refreshInviteTokensStep/index.html.md) +- [validateTokenStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateTokenStep/index.html.md) +- [deleteFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteFilesStep/index.html.md) +- [uploadFilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/uploadFilesStep/index.html.md) - [adjustInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/adjustInventoryLevelsStep/index.html.md) - [attachInventoryItemToVariants](https://docs.medusajs.com/references/medusa-workflows/steps/attachInventoryItemToVariants/index.html.md) - [createInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryItemsStep/index.html.md) - [createInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInventoryLevelsStep/index.html.md) +- [updateInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryItemsStep/index.html.md) - [deleteInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryLevelsStep/index.html.md) - [deleteInventoryItemStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInventoryItemStep/index.html.md) -- [updateInventoryItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryItemsStep/index.html.md) -- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md) - [updateInventoryLevelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateInventoryLevelsStep/index.html.md) -- [validateInventoryItemsForCreate](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryItemsForCreate/index.html.md) +- [validateInventoryDeleteStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryDeleteStep/index.html.md) - [validateInventoryLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryLocationsStep/index.html.md) -- [deleteInvitesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteInvitesStep/index.html.md) -- [createInviteStep](https://docs.medusajs.com/references/medusa-workflows/steps/createInviteStep/index.html.md) -- [refreshInviteTokensStep](https://docs.medusajs.com/references/medusa-workflows/steps/refreshInviteTokensStep/index.html.md) -- [validateTokenStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateTokenStep/index.html.md) +- [validateInventoryItemsForCreate](https://docs.medusajs.com/references/medusa-workflows/steps/validateInventoryItemsForCreate/index.html.md) - [deleteLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteLineItemsStep/index.html.md) - [updateLineItemsStepWithSelector](https://docs.medusajs.com/references/medusa-workflows/steps/updateLineItemsStepWithSelector/index.html.md) - [listLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listLineItemsStep/index.html.md) +- [authorizePaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/authorizePaymentSessionStep/index.html.md) +- [cancelPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelPaymentStep/index.html.md) +- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/index.html.md) +- [refundPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentsStep/index.html.md) +- [refundPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentStep/index.html.md) - [addOrderTransactionStep](https://docs.medusajs.com/references/medusa-workflows/steps/addOrderTransactionStep/index.html.md) - [archiveOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/archiveOrdersStep/index.html.md) - [cancelOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderChangeStep/index.html.md) - [cancelOrderClaimStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderClaimStep/index.html.md) - [cancelOrderExchangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderExchangeStep/index.html.md) - [cancelOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderFulfillmentStep/index.html.md) -- [cancelOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrdersStep/index.html.md) - [cancelOrderReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrderReturnStep/index.html.md) +- [cancelOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelOrdersStep/index.html.md) - [completeOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/completeOrdersStep/index.html.md) - [createCompleteReturnStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCompleteReturnStep/index.html.md) - [createOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderChangeStep/index.html.md) -- [createOrderClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimsStep/index.html.md) - [createOrderClaimItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimItemsFromActionsStep/index.html.md) -- [createOrderExchangeItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangeItemsFromActionsStep/index.html.md) +- [createOrderClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderClaimsStep/index.html.md) - [createOrderLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderLineItemsStep/index.html.md) - [createOrderExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangesStep/index.html.md) +- [createOrderExchangeItemsFromActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrderExchangeItemsFromActionsStep/index.html.md) - [createOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createOrdersStep/index.html.md) - [createReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnsStep/index.html.md) -- [deleteClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteClaimsStep/index.html.md) - [declineOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/declineOrderChangeStep/index.html.md) +- [deleteClaimsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteClaimsStep/index.html.md) - [deleteExchangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteExchangesStep/index.html.md) -- [deleteOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangeActionsStep/index.html.md) - [deleteOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangesStep/index.html.md) -- [deleteOrderShippingMethods](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderShippingMethods/index.html.md) - [deleteOrderLineItems](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderLineItems/index.html.md) +- [deleteOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderChangeActionsStep/index.html.md) +- [deleteOrderShippingMethods](https://docs.medusajs.com/references/medusa-workflows/steps/deleteOrderShippingMethods/index.html.md) - [deleteReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnsStep/index.html.md) -- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md) -- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md) - [previewOrderChangeStep](https://docs.medusajs.com/references/medusa-workflows/steps/previewOrderChangeStep/index.html.md) +- [registerOrderFulfillmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderFulfillmentStep/index.html.md) - [registerOrderShipmentStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderShipmentStep/index.html.md) +- [registerOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/registerOrderChangesStep/index.html.md) - [setOrderTaxLinesForItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/setOrderTaxLinesForItemsStep/index.html.md) - [updateOrderChangesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangesStep/index.html.md) - [updateOrderChangeActionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrderChangeActionsStep/index.html.md) @@ -26116,106 +26121,94 @@ For each product variant, you: - [updateOrdersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateOrdersStep/index.html.md) - [updateReturnItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnItemsStep/index.html.md) - [updateReturnsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnsStep/index.html.md) -- [sendNotificationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/sendNotificationsStep/index.html.md) -- [notifyOnFailureStep](https://docs.medusajs.com/references/medusa-workflows/steps/notifyOnFailureStep/index.html.md) -- [authorizePaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/authorizePaymentSessionStep/index.html.md) -- [cancelPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/cancelPaymentStep/index.html.md) -- [capturePaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/capturePaymentStep/index.html.md) -- [refundPaymentStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentStep/index.html.md) -- [refundPaymentsStep](https://docs.medusajs.com/references/medusa-workflows/steps/refundPaymentsStep/index.html.md) - [createPaymentAccountHolderStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentAccountHolderStep/index.html.md) -- [createPaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentSessionStep/index.html.md) - [createRefundReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRefundReasonStep/index.html.md) +- [createPaymentSessionStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPaymentSessionStep/index.html.md) +- [deletePaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePaymentSessionsStep/index.html.md) - [deleteRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRefundReasonsStep/index.html.md) - [updatePaymentCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePaymentCollectionStep/index.html.md) -- [deletePaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePaymentSessionsStep/index.html.md) -- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md) - [updateRefundReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRefundReasonsStep/index.html.md) -- [createPriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListsStep/index.html.md) +- [validateDeletedPaymentSessionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateDeletedPaymentSessionsStep/index.html.md) +- [notifyOnFailureStep](https://docs.medusajs.com/references/medusa-workflows/steps/notifyOnFailureStep/index.html.md) +- [sendNotificationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/sendNotificationsStep/index.html.md) - [createPriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListPricesStep/index.html.md) - [deletePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePriceListsStep/index.html.md) +- [createPriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceListsStep/index.html.md) - [getExistingPriceListsPriceIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getExistingPriceListsPriceIdsStep/index.html.md) - [updatePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListPricesStep/index.html.md) -- [updatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListsStep/index.html.md) -- [validatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePriceListsStep/index.html.md) - [removePriceListPricesStep](https://docs.medusajs.com/references/medusa-workflows/steps/removePriceListPricesStep/index.html.md) +- [validatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/validatePriceListsStep/index.html.md) +- [updatePriceListsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceListsStep/index.html.md) - [validateVariantPriceLinksStep](https://docs.medusajs.com/references/medusa-workflows/steps/validateVariantPriceLinksStep/index.html.md) - [createPricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPricePreferencesStep/index.html.md) - [updatePricePreferencesAsArrayStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesAsArrayStep/index.html.md) -- [updatePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesStep/index.html.md) - [deletePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePricePreferencesStep/index.html.md) - [createPriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPriceSetsStep/index.html.md) +- [updatePricePreferencesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePricePreferencesStep/index.html.md) - [updatePriceSetsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePriceSetsStep/index.html.md) - [batchLinkProductsToCategoryStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCategoryStep/index.html.md) - [batchLinkProductsToCollectionStep](https://docs.medusajs.com/references/medusa-workflows/steps/batchLinkProductsToCollectionStep/index.html.md) - [createCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCollectionsStep/index.html.md) - [createProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductOptionsStep/index.html.md) -- [createProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTypesStep/index.html.md) -- [createProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductVariantsStep/index.html.md) - [createProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTagsStep/index.html.md) - [createProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductsStep/index.html.md) +- [createProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductVariantsStep/index.html.md) +- [createProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductTypesStep/index.html.md) - [createVariantPricingLinkStep](https://docs.medusajs.com/references/medusa-workflows/steps/createVariantPricingLinkStep/index.html.md) - [deleteProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductOptionsStep/index.html.md) -- [deleteCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCollectionsStep/index.html.md) - [deleteProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTagsStep/index.html.md) +- [deleteCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCollectionsStep/index.html.md) - [deleteProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductTypesStep/index.html.md) +- [deleteProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductVariantsStep/index.html.md) - [deleteProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductsStep/index.html.md) - [generateProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/generateProductCsvStep/index.html.md) - [getAllProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getAllProductsStep/index.html.md) -- [deleteProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductVariantsStep/index.html.md) +- [getProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getProductsStep/index.html.md) - [getVariantAvailabilityStep](https://docs.medusajs.com/references/medusa-workflows/steps/getVariantAvailabilityStep/index.html.md) - [groupProductsForBatchStep](https://docs.medusajs.com/references/medusa-workflows/steps/groupProductsForBatchStep/index.html.md) -- [getProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/getProductsStep/index.html.md) - [parseProductCsvStep](https://docs.medusajs.com/references/medusa-workflows/steps/parseProductCsvStep/index.html.md) +- [updateCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCollectionsStep/index.html.md) - [updateProductOptionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductOptionsStep/index.html.md) - [updateProductTagsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductTagsStep/index.html.md) -- [updateCollectionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCollectionsStep/index.html.md) - [updateProductTypesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductTypesStep/index.html.md) -- [updateProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductsStep/index.html.md) - [updateProductVariantsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductVariantsStep/index.html.md) +- [updateProductsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductsStep/index.html.md) - [waitConfirmationProductImportStep](https://docs.medusajs.com/references/medusa-workflows/steps/waitConfirmationProductImportStep/index.html.md) - [createProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createProductCategoriesStep/index.html.md) - [deleteProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteProductCategoriesStep/index.html.md) - [updateProductCategoriesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateProductCategoriesStep/index.html.md) +- [deleteReturnReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnReasonStep/index.html.md) +- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md) +- [updateReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnReasonsStep/index.html.md) +- [createRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRegionsStep/index.html.md) +- [deleteRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRegionsStep/index.html.md) +- [updateRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRegionsStep/index.html.md) +- [setRegionsPaymentProvidersStep](https://docs.medusajs.com/references/medusa-workflows/steps/setRegionsPaymentProvidersStep/index.html.md) - [addCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addCampaignPromotionsStep/index.html.md) +- [addRulesToPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addRulesToPromotionsStep/index.html.md) - [createCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createCampaignsStep/index.html.md) - [deleteCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteCampaignsStep/index.html.md) -- [addRulesToPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/addRulesToPromotionsStep/index.html.md) -- [createPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPromotionsStep/index.html.md) - [deletePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deletePromotionsStep/index.html.md) -- [updateCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCampaignsStep/index.html.md) +- [createPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createPromotionsStep/index.html.md) - [removeCampaignPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeCampaignPromotionsStep/index.html.md) - [removeRulesFromPromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/removeRulesFromPromotionsStep/index.html.md) - [updatePromotionRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionRulesStep/index.html.md) +- [updateCampaignsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateCampaignsStep/index.html.md) - [updatePromotionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updatePromotionsStep/index.html.md) -- [createRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createRegionsStep/index.html.md) -- [deleteRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteRegionsStep/index.html.md) -- [updateRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateRegionsStep/index.html.md) -- [setRegionsPaymentProvidersStep](https://docs.medusajs.com/references/medusa-workflows/steps/setRegionsPaymentProvidersStep/index.html.md) -- [createReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReservationsStep/index.html.md) -- [deleteReservationsByLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsByLineItemsStep/index.html.md) -- [deleteReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsStep/index.html.md) -- [updateReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReservationsStep/index.html.md) - [associateProductsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateProductsWithSalesChannelsStep/index.html.md) - [associateLocationsWithSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/associateLocationsWithSalesChannelsStep/index.html.md) -- [createDefaultSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultSalesChannelStep/index.html.md) -- [canDeleteSalesChannelsOrThrowStep](https://docs.medusajs.com/references/medusa-workflows/steps/canDeleteSalesChannelsOrThrowStep/index.html.md) - [createSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createSalesChannelsStep/index.html.md) -- [detachLocationsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachLocationsFromSalesChannelsStep/index.html.md) -- [detachProductsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachProductsFromSalesChannelsStep/index.html.md) +- [canDeleteSalesChannelsOrThrowStep](https://docs.medusajs.com/references/medusa-workflows/steps/canDeleteSalesChannelsOrThrowStep/index.html.md) +- [createDefaultSalesChannelStep](https://docs.medusajs.com/references/medusa-workflows/steps/createDefaultSalesChannelStep/index.html.md) - [deleteSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteSalesChannelsStep/index.html.md) +- [detachLocationsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachLocationsFromSalesChannelsStep/index.html.md) - [updateSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateSalesChannelsStep/index.html.md) -- [createReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReturnReasonsStep/index.html.md) -- [updateReturnReasonsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReturnReasonsStep/index.html.md) -- [deleteReturnReasonStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReturnReasonStep/index.html.md) +- [detachProductsFromSalesChannelsStep](https://docs.medusajs.com/references/medusa-workflows/steps/detachProductsFromSalesChannelsStep/index.html.md) +- [deleteShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingProfilesStep/index.html.md) - [listShippingOptionsForContextStep](https://docs.medusajs.com/references/medusa-workflows/steps/listShippingOptionsForContextStep/index.html.md) - [createStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/createStoresStep/index.html.md) -- [updateStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStoresStep/index.html.md) - [deleteStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStoresStep/index.html.md) -- [deleteShippingProfilesStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteShippingProfilesStep/index.html.md) -- [createStockLocations](https://docs.medusajs.com/references/medusa-workflows/steps/createStockLocations/index.html.md) -- [updateStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStockLocationsStep/index.html.md) -- [deleteStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStockLocationsStep/index.html.md) +- [updateStoresStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStoresStep/index.html.md) - [createTaxRateRulesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRateRulesStep/index.html.md) - [createTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRatesStep/index.html.md) - [createTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createTaxRegionsStep/index.html.md) @@ -26226,34 +26219,103 @@ For each product variant, you: - [listTaxRateIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateIdsStep/index.html.md) - [listTaxRateRuleIdsStep](https://docs.medusajs.com/references/medusa-workflows/steps/listTaxRateRuleIdsStep/index.html.md) - [updateTaxRegionsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRegionsStep/index.html.md) +- [createStockLocations](https://docs.medusajs.com/references/medusa-workflows/steps/createStockLocations/index.html.md) +- [deleteStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteStockLocationsStep/index.html.md) +- [updateStockLocationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateStockLocationsStep/index.html.md) - [updateTaxRatesStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateTaxRatesStep/index.html.md) - [createUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/createUsersStep/index.html.md) -- [updateUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateUsersStep/index.html.md) - [deleteUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteUsersStep/index.html.md) +- [updateUsersStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateUsersStep/index.html.md) +- [deleteReservationsByLineItemsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsByLineItemsStep/index.html.md) +- [createReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/createReservationsStep/index.html.md) +- [deleteReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/deleteReservationsStep/index.html.md) +- [updateReservationsStep](https://docs.medusajs.com/references/medusa-workflows/steps/updateReservationsStep/index.html.md) + + +# Medusa CLI Reference + +The Medusa CLI tool provides commands that facilitate your development. + +### Prerequisites + +- [Node.js v20+](https://nodejs.org/en/download) +- [Git CLI tool](https://git-scm.com/downloads) +- [PostgreSQL](https://www.postgresql.org/download/) + +## Usage + +In your Medusa application's directory, you can use the Medusa CLI tool using NPX. + +For example: + +```bash +npx medusa --help +``` + +*** + + +# build Command - Medusa CLI Reference + +Create a standalone build of the Medusa application. + +This creates a build that: + +- Doesn't rely on the source TypeScript files. +- Can be copied to a production server reliably. +The build is outputted to a new `.medusa/server` directory. -# Medusa CLI Reference +```bash +npx medusa build +``` -The Medusa CLI tool provides commands that facilitate your development. +Refer to [this section](#run-built-medusa-application) for next steps. -### Prerequisites +## Options -- [Node.js v20+](https://nodejs.org/en/download) -- [Git CLI tool](https://git-scm.com/downloads) -- [PostgreSQL](https://www.postgresql.org/download/) +|Option|Description| +|---|---|---| +|\`--admin-only\`|Whether to only build the admin to host it separately. If this option is not passed, the admin is built to the | -## Usage +*** -In your Medusa application's directory, you can use the Medusa CLI tool using NPX. +## Run Built Medusa Application -For example: +After running the `build` command, use the following step to run the built Medusa application: + +- Change to the `.medusa/server` directory and install the dependencies: + +```bash npm2yarn +cd .medusa/server && npm install +``` + +- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead. + +```bash npm2yarn +cp .env .medusa/server/.env.production +``` + +- In the system environment variables, set `NODE_ENV` to `production`: ```bash -npx medusa --help +NODE_ENV=production +``` + +- Use the `start` command to run the application: + +```bash npm2yarn +cd .medusa/server && npm run start ``` *** +## Build Medusa Admin + +By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory. + +If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead. + # db Commands - Medusa CLI Reference @@ -26375,84 +26437,6 @@ npx medusa db:sync-links |\`--execute-all\`|Skip prompts when syncing links and execute all (including unsafe) actions.|No|Prompts are shown for unsafe actions, by default.| -# build Command - Medusa CLI Reference - -Create a standalone build of the Medusa application. - -This creates a build that: - -- Doesn't rely on the source TypeScript files. -- Can be copied to a production server reliably. - -The build is outputted to a new `.medusa/server` directory. - -```bash -npx medusa build -``` - -Refer to [this section](#run-built-medusa-application) for next steps. - -## Options - -|Option|Description| -|---|---|---| -|\`--admin-only\`|Whether to only build the admin to host it separately. If this option is not passed, the admin is built to the | - -*** - -## Run Built Medusa Application - -After running the `build` command, use the following step to run the built Medusa application: - -- Change to the `.medusa/server` directory and install the dependencies: - -```bash npm2yarn -cd .medusa/server && npm install -``` - -- When running the application locally, make sure to copy the `.env` file from the root project's directory. In production, use system environment variables instead. - -```bash npm2yarn -cp .env .medusa/server/.env.production -``` - -- In the system environment variables, set `NODE_ENV` to `production`: - -```bash -NODE_ENV=production -``` - -- Use the `start` command to run the application: - -```bash npm2yarn -cd .medusa/server && npm run start -``` - -*** - -## Build Medusa Admin - -By default, the Medusa Admin is built to the `.medusa/server/public/admin` directory. - -If you want a separate build to host the admin standalone, such as on Vercel, pass the `--admin-only` option as explained in the [Options](#options) section. This outputs the admin to the `.medusa/admin` directory instead. - - -# develop Command - Medusa CLI Reference - -Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application. - -```bash -npx medusa develop -``` - -## Options - -|Option|Description|Default| -|---|---|---|---|---| -|\`-H \\`|Set host of the Medusa server.|\`localhost\`| -|\`-p \\`|Set port of the Medusa server.|\`9000\`| - - # exec Command - Medusa CLI Reference Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md). @@ -26469,12 +26453,12 @@ npx medusa exec [file] [args...] |\`args\`|A list of arguments to pass to the function. These arguments are passed in the |No| -# start Command - Medusa CLI Reference +# develop Command - Medusa CLI Reference -Start the Medusa application in production. +Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application. ```bash -npx medusa start +npx medusa develop ``` ## Options @@ -26514,25 +26498,6 @@ medusa new [ []] |\`--db-host \\`|The database host to use for database setup.| -# start-cluster Command - Medusa CLI Reference - -Starts the Medusa application in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster). - -Running in cluster mode significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one. - -```bash -npx medusa start-cluster -``` - -#### Options - -|Option|Description|Default| -|---|---|---|---|---| -|\`-c \\`|The number of CPUs that Medusa can consume.|Medusa will try to consume all CPUs.| -|\`-H \\`|Set host of the Medusa server.|\`localhost\`| -|\`-p \\`|Set port of the Medusa server.|\`9000\`| - - # plugin Commands - Medusa CLI Reference Commands starting with `plugin:` perform actions related to [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) development. @@ -26594,20 +26559,39 @@ npx medusa plugin:build ``` -# telemetry Command - Medusa CLI Reference +# start Command - Medusa CLI Reference -Enable or disable the collection of anonymous data usage. If no option is provided, the command enables the collection of anonymous data usage. +Start the Medusa application in production. ```bash -npx medusa telemetry +npx medusa start +``` + +## Options + +|Option|Description|Default| +|---|---|---|---|---| +|\`-H \\`|Set host of the Medusa server.|\`localhost\`| +|\`-p \\`|Set port of the Medusa server.|\`9000\`| + + +# start-cluster Command - Medusa CLI Reference + +Starts the Medusa application in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster). + +Running in cluster mode significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one. + +```bash +npx medusa start-cluster ``` #### Options -|Option|Description| -|---|---|---| -|\`--enable\`|Enable telemetry (default).| -|\`--disable\`|Disable telemetry.| +|Option|Description|Default| +|---|---|---|---|---| +|\`-c \\`|The number of CPUs that Medusa can consume.|Medusa will try to consume all CPUs.| +|\`-H \\`|Set host of the Medusa server.|\`localhost\`| +|\`-p \\`|Set port of the Medusa server.|\`9000\`| # user Command - Medusa CLI Reference @@ -26629,6 +26613,22 @@ npx medusa user --email [--password ] If ran successfully, you'll receive the invite token in the output.|No|\`false\`| +# telemetry Command - Medusa CLI Reference + +Enable or disable the collection of anonymous data usage. If no option is provided, the command enables the collection of anonymous data usage. + +```bash +npx medusa telemetry +``` + +#### Options + +|Option|Description| +|---|---|---| +|\`--enable\`|Enable telemetry (default).| +|\`--disable\`|Disable telemetry.| + + # Medusa CLI Reference The Medusa CLI tool provides commands that facilitate your development. @@ -26834,6 +26834,22 @@ npx medusa db:sync-links |\`--execute-all\`|Skip prompts when syncing links and execute all (including unsafe) actions.|No|Prompts are shown for unsafe actions, by default.| +# develop Command - Medusa CLI Reference + +Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application. + +```bash +npx medusa develop +``` + +## Options + +|Option|Description|Default| +|---|---|---|---|---| +|\`-H \\`|Set host of the Medusa server.|\`localhost\`| +|\`-p \\`|Set port of the Medusa server.|\`9000\`| + + # exec Command - Medusa CLI Reference Run a custom CLI script. Learn more about it in [this guide](https://docs.medusajs.com/docs/learn/fundamentals/custom-cli-scripts/index.html.md). @@ -26879,22 +26895,6 @@ medusa new [ []] |\`--db-host \\`|The database host to use for database setup.| -# develop Command - Medusa CLI Reference - -Start Medusa application in development. This command watches files for any changes, then rebuilds the files and restarts the Medusa application. - -```bash -npx medusa develop -``` - -## Options - -|Option|Description|Default| -|---|---|---|---|---| -|\`-H \\`|Set host of the Medusa server.|\`localhost\`| -|\`-p \\`|Set port of the Medusa server.|\`9000\`| - - # plugin Commands - Medusa CLI Reference Commands starting with `plugin:` perform actions related to [plugin](https://docs.medusajs.com/docs/learn/fundamentals/plugins/index.html.md) development. @@ -26956,37 +26956,53 @@ npx medusa plugin:build ``` -# start Command - Medusa CLI Reference +# start-cluster Command - Medusa CLI Reference -Start the Medusa application in production. +Starts the Medusa application in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster). + +Running in cluster mode significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one. ```bash -npx medusa start +npx medusa start-cluster ``` -## Options +#### Options |Option|Description|Default| |---|---|---|---|---| +|\`-c \\`|The number of CPUs that Medusa can consume.|Medusa will try to consume all CPUs.| |\`-H \\`|Set host of the Medusa server.|\`localhost\`| |\`-p \\`|Set port of the Medusa server.|\`9000\`| -# start-cluster Command - Medusa CLI Reference +# telemetry Command - Medusa CLI Reference + +Enable or disable the collection of anonymous data usage. If no option is provided, the command enables the collection of anonymous data usage. + +```bash +npx medusa telemetry +``` + +#### Options + +|Option|Description| +|---|---|---| +|\`--enable\`|Enable telemetry (default).| +|\`--disable\`|Disable telemetry.| + -Starts the Medusa application in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster). +# start Command - Medusa CLI Reference -Running in cluster mode significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one. +Start the Medusa application in production. ```bash -npx medusa start-cluster +npx medusa start ``` -#### Options +## Options |Option|Description|Default| |---|---|---|---|---| -|\`-c \\`|The number of CPUs that Medusa can consume.|Medusa will try to consume all CPUs.| |\`-H \\`|Set host of the Medusa server.|\`localhost\`| |\`-p \\`|Set port of the Medusa server.|\`9000\`| @@ -27010,22 +27026,6 @@ npx medusa user --email [--password ] If ran successfully, you'll receive the invite token in the output.|No|\`false\`| -# telemetry Command - Medusa CLI Reference - -Enable or disable the collection of anonymous data usage. If no option is provided, the command enables the collection of anonymous data usage. - -```bash -npx medusa telemetry -``` - -#### Options - -|Option|Description| -|---|---|---| -|\`--enable\`|Enable telemetry (default).| -|\`--disable\`|Disable telemetry.| - - # Medusa JS SDK In this documentation, you'll learn how to install and use Medusa's JS SDK. @@ -27328,9 +27328,9 @@ The object or class passed to `auth.storage` configuration must have the followi - [batchSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.batchSalesChannels/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.create/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.list/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.delete/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.list/index.html.md) - [revoke](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.revoke/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ApiKey/methods/js_sdk.admin.ApiKey.update/index.html.md) - [batchPromotions](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.batchPromotions/index.html.md) @@ -27338,1593 +27338,1282 @@ The object or class passed to `auth.storage` configuration must have the followi - [delete](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.update/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.retrieve/index.html.md) - [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundItems/index.html.md) - [addItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addItems/index.html.md) - [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addInboundShipping/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/Campaign/methods/js_sdk.admin.Campaign.update/index.html.md) -- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundItems/index.html.md) - [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundShipping/index.html.md) -- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancelRequest/index.html.md) +- [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.addOutboundItems/index.html.md) - [cancel](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancel/index.html.md) -- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteInboundShipping/index.html.md) +- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.cancelRequest/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.create/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.list/index.html.md) +- [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteInboundShipping/index.html.md) +- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteOutboundShipping/index.html.md) - [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeInboundItem/index.html.md) - [removeItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeItem/index.html.md) -- [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.deleteOutboundShipping/index.html.md) - [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.removeOutboundItem/index.html.md) - [request](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.request/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.retrieve/index.html.md) - [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundItem/index.html.md) +- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundItem/index.html.md) - [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateInboundShipping/index.html.md) - [updateItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateItem/index.html.md) -- [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundItem/index.html.md) - [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Claim/methods/js_sdk.admin.Claim.updateOutboundShipping/index.html.md) +- [getItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.getItem/index.html.md) +- [removeItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.removeItem/index.html.md) +- [setItem](https://docs.medusajs.com/references/js_sdk/admin/CustomStorage/methods/js_sdk.admin.CustomStorage.setItem/index.html.md) - [batchCustomers](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.batchCustomers/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.retrieve/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/CustomerGroup/methods/js_sdk.admin.CustomerGroup.update/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Currency/methods/js_sdk.admin.Currency.retrieve/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.delete/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.create/index.html.md) +- [clearToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken/index.html.md) +- [clearToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken_/index.html.md) +- [fetch](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetch/index.html.md) +- [fetchStream](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetchStream/index.html.md) +- [getApiKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getApiKeyHeader_/index.html.md) +- [getJwtHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getJwtHeader_/index.html.md) +- [getPublishableKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getPublishableKeyHeader_/index.html.md) +- [getTokenStorageInfo\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getTokenStorageInfo_/index.html.md) +- [getToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getToken_/index.html.md) +- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md) +- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md) +- [setToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken_/index.html.md) +- [throwError\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.throwError_/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.create/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/DraftOrder/methods/js_sdk.admin.DraftOrder.update/index.html.md) - [batchCustomerGroups](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.batchCustomerGroups/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.list/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.update/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Customer/methods/js_sdk.admin.Customer.update/index.html.md) - [addInboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundItems/index.html.md) - [addOutboundItems](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundItems/index.html.md) -- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundShipping/index.html.md) - [addOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addOutboundShipping/index.html.md) +- [addInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.addInboundShipping/index.html.md) - [cancel](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancel/index.html.md) - [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.cancelRequest/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.create/index.html.md) - [deleteInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteInboundShipping/index.html.md) - [deleteOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.deleteOutboundShipping/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.list/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.create/index.html.md) - [removeInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeInboundItem/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.retrieve/index.html.md) - [removeOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.removeOutboundItem/index.html.md) - [request](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.request/index.html.md) - [updateInboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundItem/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.retrieve/index.html.md) +- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundShipping/index.html.md) - [updateOutboundItem](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundItem/index.html.md) - [updateOutboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateOutboundShipping/index.html.md) -- [updateInboundShipping](https://docs.medusajs.com/references/js_sdk/admin/Exchange/methods/js_sdk.admin.Exchange.updateInboundShipping/index.html.md) -- [fetch](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetch/index.html.md) -- [fetchStream](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.fetchStream/index.html.md) -- [clearToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken_/index.html.md) -- [clearToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.clearToken/index.html.md) -- [getApiKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getApiKeyHeader_/index.html.md) -- [getJwtHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getJwtHeader_/index.html.md) -- [getPublishableKeyHeader\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getPublishableKeyHeader_/index.html.md) -- [getTokenStorageInfo\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getTokenStorageInfo_/index.html.md) -- [initClient](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.initClient/index.html.md) -- [getToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.getToken_/index.html.md) -- [setToken](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken/index.html.md) -- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.createShipment/index.html.md) -- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.cancel/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.create/index.html.md) -- [setToken\_](https://docs.medusajs.com/references/js_sdk/admin/Client/methods/js_sdk.admin.Client.setToken_/index.html.md) -- [listFulfillmentOptions](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.listFulfillmentOptions/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.list/index.html.md) -- [createServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.createServiceZone/index.html.md) -- [retrieveServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.retrieveServiceZone/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.delete/index.html.md) +- [createServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.createServiceZone/index.html.md) - [deleteServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.deleteServiceZone/index.html.md) +- [retrieveServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.retrieveServiceZone/index.html.md) - [updateServiceZone](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentSet/methods/js_sdk.admin.FulfillmentSet.updateServiceZone/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.list/index.html.md) +- [listFulfillmentOptions](https://docs.medusajs.com/references/js_sdk/admin/FulfillmentProvider/methods/js_sdk.admin.FulfillmentProvider.listFulfillmentOptions/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.create/index.html.md) +- [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.createShipment/index.html.md) +- [batchInventoryItemLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemLocationLevels/index.html.md) +- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Fulfillment/methods/js_sdk.admin.Fulfillment.cancel/index.html.md) +- [batchInventoryItemsLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemsLocationLevels/index.html.md) +- [batchUpdateLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchUpdateLevels/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md) +- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.list/index.html.md) +- [listLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.listLevels/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.update/index.html.md) +- [updateLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.updateLevel/index.html.md) - [accept](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.accept/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.delete/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.create/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.list/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.delete/index.html.md) - [resend](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.resend/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Invite/methods/js_sdk.admin.Invite.retrieve/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.retrieve/index.html.md) -- [cancelTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelTransfer/index.html.md) -- [cancelFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelFulfillment/index.html.md) - [cancel](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancel/index.html.md) +- [cancelFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelFulfillment/index.html.md) - [createFulfillment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createFulfillment/index.html.md) - [createShipment](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.createShipment/index.html.md) -- [listChanges](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listChanges/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.list/index.html.md) -- [requestTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.requestTransfer/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrieve/index.html.md) +- [cancelTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.cancelTransfer/index.html.md) +- [listChanges](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listChanges/index.html.md) - [markAsDelivered](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.markAsDelivered/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrieve/index.html.md) +- [requestTransfer](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.requestTransfer/index.html.md) - [listLineItems](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.listLineItems/index.html.md) - [retrievePreview](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.retrievePreview/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Notification/methods/js_sdk.admin.Notification.list/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.update/index.html.md) - [addItems](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.addItems/index.html.md) -- [confirm](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.confirm/index.html.md) - [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.cancelRequest/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/Order/methods/js_sdk.admin.Order.update/index.html.md) - [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.initiateRequest/index.html.md) +- [request](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.request/index.html.md) +- [confirm](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.confirm/index.html.md) - [removeAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.removeAddedItem/index.html.md) - [updateAddedItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateAddedItem/index.html.md) -- [request](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.request/index.html.md) - [updateOriginalItem](https://docs.medusajs.com/references/js_sdk/admin/OrderEdit/methods/js_sdk.admin.OrderEdit.updateOriginalItem/index.html.md) -- [batchInventoryItemsLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemsLocationLevels/index.html.md) -- [batchInventoryItemLocationLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchInventoryItemLocationLevels/index.html.md) -- [batchUpdateLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.batchUpdateLevels/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.delete/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.create/index.html.md) -- [deleteLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.deleteLevel/index.html.md) -- [listLevels](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.listLevels/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.retrieve/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.create/index.html.md) -- [updateLevel](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.updateLevel/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/InventoryItem/methods/js_sdk.admin.InventoryItem.update/index.html.md) -- [markAsPaid](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.markAsPaid/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.delete/index.html.md) -- [batchPrices](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.batchPrices/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.create/index.html.md) -- [linkProducts](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.linkProducts/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.retrieve/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.delete/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.update/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.list/index.html.md) - [capture](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.capture/index.html.md) - [listPaymentProviders](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.listPaymentProviders/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.retrieve/index.html.md) - [refund](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.refund/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.retrieve/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Payment/methods/js_sdk.admin.Payment.retrieve/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.delete/index.html.md) +- [markAsPaid](https://docs.medusajs.com/references/js_sdk/admin/PaymentCollection/methods/js_sdk.admin.PaymentCollection.markAsPaid/index.html.md) - [batch](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batch/index.html.md) - [batchVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariants/index.html.md) +- [confirmImport](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.confirmImport/index.html.md) +- [batchVariantInventoryItems](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariantInventoryItems/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.create/index.html.md) +- [createOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createOption/index.html.md) - [createVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createVariant/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.delete/index.html.md) -- [createOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.createOption/index.html.md) -- [confirmImport](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.confirmImport/index.html.md) -- [batchVariantInventoryItems](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.batchVariantInventoryItems/index.html.md) - [deleteOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteOption/index.html.md) - [deleteVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.deleteVariant/index.html.md) -- [export](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.export/index.html.md) - [import](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.import/index.html.md) +- [export](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.export/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.list/index.html.md) - [listOptions](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listOptions/index.html.md) +- [retrieveOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveOption/index.html.md) - [listVariants](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.listVariants/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieve/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.update/index.html.md) -- [retrieveOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveOption/index.html.md) - [retrieveVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.retrieveVariant/index.html.md) - [updateOption](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateOption/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md) - [updateVariant](https://docs.medusajs.com/references/js_sdk/admin/Product/methods/js_sdk.admin.Product.updateVariant/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.create/index.html.md) +- [batchPrices](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.batchPrices/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.delete/index.html.md) +- [linkProducts](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.linkProducts/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.list/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/PriceList/methods/js_sdk.admin.PriceList.update/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.delete/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.create/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/PricePreference/methods/js_sdk.admin.PricePreference.update/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.delete/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.create/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.retrieve/index.html.md) -- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.updateProducts/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.update/index.html.md) +- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCategory/methods/js_sdk.admin.ProductCategory.updateProducts/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.update/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.retrieve/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.update/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.retrieve/index.html.md) - [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/ProductCollection/methods/js_sdk.admin.ProductCollection.updateProducts/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.create/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.delete/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.retrieve/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.list/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/ProductTag/methods/js_sdk.admin.ProductTag.update/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.delete/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.retrieve/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.create/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.list/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.delete/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ProductType/methods/js_sdk.admin.ProductType.update/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/ProductVariant/methods/js_sdk.admin.ProductVariant.list/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.create/index.html.md) - [addRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.addRules/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.list/index.html.md) -- [listRuleValues](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleValues/index.html.md) - [listRuleAttributes](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleAttributes/index.html.md) +- [listRuleValues](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRuleValues/index.html.md) - [listRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.listRules/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md) - [removeRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.removeRules/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.retrieve/index.html.md) - [updateRules](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.updateRules/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.delete/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.list/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.create/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.update/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Promotion/methods/js_sdk.admin.Promotion.update/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.delete/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.list/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.update/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.retrieve/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Region/methods/js_sdk.admin.Region.list/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.delete/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.list/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.update/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Reservation/methods/js_sdk.admin.Reservation.retrieve/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/RefundReason/methods/js_sdk.admin.RefundReason.list/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.delete/index.html.md) +- [addReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnShipping/index.html.md) +- [addReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnItem/index.html.md) +- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancel/index.html.md) +- [cancelReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelReceive/index.html.md) +- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelRequest/index.html.md) +- [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/index.html.md) +- [confirmRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmRequest/index.html.md) +- [dismissItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.dismissItems/index.html.md) +- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md) +- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md) +- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md) +- [receiveItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.receiveItems/index.html.md) +- [removeDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeDismissItem/index.html.md) +- [removeReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReceiveItem/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.retrieve/index.html.md) +- [removeReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReturnItem/index.html.md) +- [updateDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateDismissItem/index.html.md) +- [updateReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReceiveItem/index.html.md) +- [updateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateRequest/index.html.md) +- [updateReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnItem/index.html.md) +- [updateReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnShipping/index.html.md) +- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.update/index.html.md) +- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.create/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.list/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.delete/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.update/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ReturnReason/methods/js_sdk.admin.ReturnReason.retrieve/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.create/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.list/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.delete/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.retrieve/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.update/index.html.md) - [updateRules](https://docs.medusajs.com/references/js_sdk/admin/ShippingOption/methods/js_sdk.admin.ShippingOption.updateRules/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.delete/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.list/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.retrieve/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/ShippingProfile/methods/js_sdk.admin.ShippingProfile.update/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.list/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.update/index.html.md) -- [batchProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.batchProducts/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.retrieve/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.create/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.delete/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.retrieve/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.list/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.update/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md) -- [updateProducts](https://docs.medusajs.com/references/js_sdk/admin/SalesChannel/methods/js_sdk.admin.SalesChannel.updateProducts/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.delete/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.retrieve/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.list/index.html.md) -- [update](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.update/index.html.md) - [createFulfillmentSet](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.createFulfillmentSet/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.delete/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.delete/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.retrieve/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.list/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.update/index.html.md) - [updateFulfillmentProviders](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateFulfillmentProviders/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.retrieve/index.html.md) - [updateSalesChannels](https://docs.medusajs.com/references/js_sdk/admin/StockLocation/methods/js_sdk.admin.StockLocation.updateSalesChannels/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.list/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.retrieve/index.html.md) -- [cancel](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancel/index.html.md) -- [cancelReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelReceive/index.html.md) -- [addReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnItem/index.html.md) -- [confirmReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmReceive/index.html.md) -- [addReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.addReturnShipping/index.html.md) -- [cancelRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.cancelRequest/index.html.md) -- [confirmRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.confirmRequest/index.html.md) -- [deleteReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.deleteReturnShipping/index.html.md) -- [dismissItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.dismissItems/index.html.md) -- [initiateReceive](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateReceive/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.list/index.html.md) -- [removeDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeDismissItem/index.html.md) -- [receiveItems](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.receiveItems/index.html.md) -- [initiateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.initiateRequest/index.html.md) -- [removeReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReceiveItem/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.retrieve/index.html.md) -- [removeReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.removeReturnItem/index.html.md) -- [updateDismissItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateDismissItem/index.html.md) -- [updateRequest](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateRequest/index.html.md) -- [updateReturnItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnItem/index.html.md) -- [updateReceiveItem](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReceiveItem/index.html.md) -- [updateReturnShipping](https://docs.medusajs.com/references/js_sdk/admin/Return/methods/js_sdk.admin.Return.updateReturnShipping/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.create/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.delete/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.list/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.update/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRate/methods/js_sdk.admin.TaxRate.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.retrieve/index.html.md) +- [update](https://docs.medusajs.com/references/js_sdk/admin/Store/methods/js_sdk.admin.Store.update/index.html.md) - [create](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.create/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.delete/index.html.md) -- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/index.html.md) +- [create](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.create/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.retrieve/index.html.md) +- [delete](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.delete/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/TaxRegion/methods/js_sdk.admin.TaxRegion.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.retrieve/index.html.md) +- [list](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.list/index.html.md) +- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/WorkflowExecution/methods/js_sdk.admin.WorkflowExecution.retrieve/index.html.md) - [delete](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.delete/index.html.md) - [list](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.list/index.html.md) - [me](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.me/index.html.md) - [retrieve](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.retrieve/index.html.md) - [update](https://docs.medusajs.com/references/js_sdk/admin/User/methods/js_sdk.admin.User.update/index.html.md) -- [delete](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.delete/index.html.md) -- [create](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.create/index.html.md) -- [retrieve](https://docs.medusajs.com/references/js_sdk/admin/Upload/methods/js_sdk.admin.Upload.retrieve/index.html.md) ## JS SDK Auth -- [login](https://docs.medusajs.com/references/js-sdk/auth/login/index.html.md) -- [register](https://docs.medusajs.com/references/js-sdk/auth/register/index.html.md) - [callback](https://docs.medusajs.com/references/js-sdk/auth/callback/index.html.md) -- [resetPassword](https://docs.medusajs.com/references/js-sdk/auth/resetPassword/index.html.md) +- [register](https://docs.medusajs.com/references/js-sdk/auth/register/index.html.md) - [logout](https://docs.medusajs.com/references/js-sdk/auth/logout/index.html.md) +- [resetPassword](https://docs.medusajs.com/references/js-sdk/auth/resetPassword/index.html.md) - [refresh](https://docs.medusajs.com/references/js-sdk/auth/refresh/index.html.md) +- [login](https://docs.medusajs.com/references/js-sdk/auth/login/index.html.md) - [updateProvider](https://docs.medusajs.com/references/js-sdk/auth/updateProvider/index.html.md) ## JS SDK Store - [cart](https://docs.medusajs.com/references/js-sdk/store/cart/index.html.md) -- [collection](https://docs.medusajs.com/references/js-sdk/store/collection/index.html.md) -- [category](https://docs.medusajs.com/references/js-sdk/store/category/index.html.md) -- [fulfillment](https://docs.medusajs.com/references/js-sdk/store/fulfillment/index.html.md) -- [order](https://docs.medusajs.com/references/js-sdk/store/order/index.html.md) -- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md) -- [product](https://docs.medusajs.com/references/js-sdk/store/product/index.html.md) -- [region](https://docs.medusajs.com/references/js-sdk/store/region/index.html.md) -- [customer](https://docs.medusajs.com/references/js-sdk/store/customer/index.html.md) - - -# Configure Medusa Backend - -In this document, you’ll learn how to create a file service in the Medusa application and the methods you must implement in it. - -The configurations for your Medusa application are in `medusa-config.ts` located in the root of your Medusa project. The configurations include configurations for database, modules, and more. - -`medusa-config.ts` exports the value returned by the `defineConfig` utility function imported from `@medusajs/framework/utils`. - -`defineConfig` accepts as a parameter an object with the following properties: - -- [projectConfig](https://docs.medusajs.com/references/medusa-config#projectconfig/index.html.md) (required): An object that holds general configurations related to the Medusa application, such as database or CORS configurations. -- [plugins](https://docs.medusajs.com/references/medusa-config#plugins/index.html.md): An array of strings or objects that hold the configurations of the plugins installed in the Medusa application. -- [admin](https://docs.medusajs.com/references/medusa-config#admin/index.html.md): An object that holds admin-related configurations. -- [modules](https://docs.medusajs.com/references/medusa-config#modules/index.html.md): An object that configures the Medusa application's modules. -- [featureFlags](https://docs.medusajs.com/references/medusa-config#featureflags/index.html.md): An object that enables or disables features guarded by a feature flag. - -For example: - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - // ... - }, - admin: { - // ... - }, - modules: { - // ... - }, - featureFlags: { - // ... - } -}) -``` - -*** - -## Environment Variables - -It's highly recommended to store the values of configurations in environment variables, then reference them within `medusa-config.ts`. - -During development, you can set your environment variables in the `.env` file at the root of your Medusa application project. In production, -setting the environment variables depends on the hosting provider. - -*** - -## projectConfig - -This property holds essential configurations related to the Medusa application, such as database and CORS configurations. - -### databaseName - -The name of the database to connect to. If the name is specified in `databaseUrl`, then you don't have to use this configuration. - -Make sure to create the PostgreSQL database before using it. You can check how to create a database in -[PostgreSQL's documentation](https://www.postgresql.org/docs/current/sql-createdatabase.html). - -#### Example - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - databaseName: process.env.DATABASE_NAME || - "medusa-store", - // ... - }, - // ... -}) -``` - -### databaseUrl - -The PostgreSQL connection URL of the database, which is of the following format: - -```bash -postgres://[user][:password]@[host][:port]/[dbname] -``` - -Where: - -- `[user]`: (required) your PostgreSQL username. If not specified, the system's username is used by default. The database user that you use must have create privileges. If you're using the `postgres` superuser, then it should have these privileges by default. Otherwise, make sure to grant your user create privileges. You can learn how to do that in [PostgreSQL's documentation](https://www.postgresql.org/docs/current/ddl-priv.html). -- `[:password]`: an optional password for the user. When provided, make sure to put `:` before the password. -- `[host]`: (required) your PostgreSQL host. When run locally, it should be `localhost`. -- `[:port]`: an optional port that the PostgreSQL server is listening on. By default, it's `5432`. When provided, make sure to put `:` before the port. -- `[dbname]`: (required) the name of the database. - -You can learn more about the connection URL format in [PostgreSQL’s documentation](https://www.postgresql.org/docs/current/libpq-connect.html). - -#### Example - -For example, set the following database URL in your environment variables: - -```bash -DATABASE_URL=postgres://postgres@localhost/medusa-store -``` - -Then, use the value in `medusa-config.ts`: - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - databaseUrl: process.env.DATABASE_URL, - // ... - }, - // ... -}) -``` - -### databaseSchema - -The database schema to connect to. This is not required to provide if you’re using the default schema, which is `public`. - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - databaseSchema: process.env.DATABASE_SCHEMA || - "custom", - // ... - }, - // ... -}) -``` - -### databaseLogging - -This configuration specifies whether database messages should be logged. - -#### Example - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - databaseLogging: false - // ... - }, - // ... -}) -``` - -### databaseDriverOptions - -This configuration is used to pass additional options to the database connection. You can pass any configuration. For example, pass the -`ssl` property that enables support for TLS/SSL connections. - -This is useful for production databases, which can be supported by setting the `rejectUnauthorized` attribute of `ssl` object to `false`. -During development, it’s recommended not to pass this option. - -:::note - -Make sure to add to the end of the database URL `?ssl_mode=disable` as well when disabling `rejectUnauthorized`. - -::: - -#### Example - -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - databaseDriverOptions: process.env.NODE_ENV !== "development" ? - { connection: { ssl: { rejectUnauthorized: false } } } : {} - // ... - }, - // ... -}) -``` - -#### Properties +- [category](https://docs.medusajs.com/references/js-sdk/store/category/index.html.md) +- [collection](https://docs.medusajs.com/references/js-sdk/store/collection/index.html.md) +- [customer](https://docs.medusajs.com/references/js-sdk/store/customer/index.html.md) +- [order](https://docs.medusajs.com/references/js-sdk/store/order/index.html.md) +- [fulfillment](https://docs.medusajs.com/references/js-sdk/store/fulfillment/index.html.md) +- [product](https://docs.medusajs.com/references/js-sdk/store/product/index.html.md) +- [payment](https://docs.medusajs.com/references/js-sdk/store/payment/index.html.md) +- [region](https://docs.medusajs.com/references/js-sdk/store/region/index.html.md) -- connection: (\`object\`) - - ssl: (\`boolean\` \\| \`ConnectionOptions\`) Configure support for TLS/SSL connection +# Configure Medusa Backend -### redisUrl +In this document, you’ll learn how to create a file service in the Medusa application and the methods you must implement in it. -This configuration specifies the connection URL to Redis to store the Medusa server's session. +The configurations for your Medusa application are set in `medusa-config.ts` located in the root of your Medusa project. The configurations include configurations for database, modules, and more. :::note -You must first have Redis installed. You can refer to [Redis's installation guide](https://redis.io/docs/getting-started/installation/). +Some Medusa configurations are set through environment variables, which you can find in [this documentation](https://docs.medusajs.com/learn/fundamentals/environment-variables#predefined-medusa-environment-variables/index.html.md). ::: -The Redis connection URL has the following format: +`medusa-config.ts` exports the value returned by the `defineConfig` utility function imported from `@medusajs/framework/utils`. -```bash -redis[s]://[[username][:password]@][host][:port][/db-number] -``` +`defineConfig` accepts as a parameter an object with the following properties: -For a local Redis installation, the connection URL should be `redis://localhost:6379` unless you’ve made any changes to the Redis configuration during installation. +- [projectConfig](https://docs.medusajs.com/references/medusa-config#projectconfig/index.html.md) (required): An object that holds general configurations related to the Medusa application, such as database or CORS configurations. +- [plugins](https://docs.medusajs.com/references/medusa-config#plugins/index.html.md): An array of strings or objects that hold the configurations of the plugins installed in the Medusa application. +- [admin](https://docs.medusajs.com/references/medusa-config#admin/index.html.md): An object that holds admin-related configurations. +- [modules](https://docs.medusajs.com/references/medusa-config#modules/index.html.md): An object that configures the Medusa application's modules. +- [featureFlags](https://docs.medusajs.com/references/medusa-config#featureflags/index.html.md): An object that enables or disables features guarded by a feature flag. -#### Example +For example: ```ts title="medusa-config.ts" module.exports = defineConfig({ projectConfig: { - redisUrl: process.env.REDIS_URL || - "redis://localhost:6379", // ... }, - // ... + admin: { + // ... + }, + modules: { + // ... + }, + featureFlags: { + // ... + } }) ``` -### redisPrefix - -This configuration defines a prefix on all keys stored in Redis for the Medusa server's session. The default value is `sess:`. +*** -If this configuration option is provided, it is prepended to `sess:`. +## Environment Variables -#### Example +It's highly recommended to store the values of configurations in environment variables, then reference them within `medusa-config.ts`. -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - redisPrefix: process.env.REDIS_URL || "medusa:", - // ... - }, - // ... -}) -``` +During development, you can set your environment variables in the `.env` file at the root of your Medusa application project. In production, +setting the environment variables depends on the hosting provider. -### redisOptions +*** -This configuration defines options to pass ioredis for the Redis connection used to store the Medusa server's session. Refer to [ioredis’s RedisOptions documentation](https://redis.github.io/ioredis/index.html#RedisOptions) -for the list of available options. +## projectConfig -#### Example +This property holds essential configurations related to the Medusa application, such as database and CORS configurations. -```ts title="medusa-config.ts" -module.exports = defineConfig({ - projectConfig: { - redisOptions: { - connectionName: process.env.REDIS_CONNECTION_NAME || - "medusa", - } - // ... - }, - // ... -}) -``` +### databaseName -### sessionOptions +The name of the database to connect to. If the name is specified in `databaseUrl`, then you don't have to use this configuration. -This configuration defines additional options to pass to [express-session](https://www.npmjs.com/package/express-session), which is used to store the Medusa server's session. +Make sure to create the PostgreSQL database before using it. You can check how to create a database in +[PostgreSQL's documentation](https://www.postgresql.org/docs/current/sql-createdatabase.html). #### Example ```ts title="medusa-config.ts" module.exports = defineConfig({ projectConfig: { - sessionOptions: { - name: process.env.SESSION_NAME || "custom", - } + databaseName: process.env.DATABASE_NAME || + "medusa-store", // ... }, // ... }) ``` -#### Properties - -- name: (\`string\`) The name of the session ID cookie to set in the response (and read from in the request). The default value is \`connect.sid\`. - Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#name) for more details. -- resave: (\`boolean\`) Whether the session should be saved back to the session store, even if the session was never modified during the request. The default value is \`true\`. - Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#resave) for more details. -- rolling: (\`boolean\`) Whether the session identifier cookie should be force-set on every response. The default value is \`false\`. - Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#rolling) for more details. -- saveUninitialized: (\`boolean\`) Whether a session that is "uninitialized" is forced to be saved to the store. The default value is \`true\`. - Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#saveUninitialized) for more details. -- secret: (\`string\`) The secret to sign the session ID cookie. By default, the value of \`http.cookieSecret\` is used. - Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#secret) for details. -- ttl: (\`number\`) Used when calculating the \`Expires\` \`Set-Cookie\` attribute of cookies. By default, its value is \`10 \* 60 \* 60 \* 1000\`. - Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#cookiemaxage) for details. - -### workerMode +### databaseUrl -Configure the application's worker mode. +The PostgreSQL connection URL of the database, which is of the following format: -Workers are processes running separately from the main application. They're useful for executing long-running or resource-heavy tasks in the background, such as importing products. +```bash +postgres://[user][:password]@[host][:port]/[dbname] +``` -With a worker, these tasks are offloaded to a separate process. So, they won't affect the performance of the main application. +Where: -![Diagram showcasing how the server and worker work together](https://res.cloudinary.com/dza7lstvk/image/upload/fl_lossy/f_auto/r_16/ar_16:9,c_pad/v1/Medusa%20Book/medusa-worker_klkbch.jpg?_a=BATFJtAA0) +- `[user]`: (required) your PostgreSQL username. If not specified, the system's username is used by default. The database user that you use must have create privileges. If you're using the `postgres` superuser, then it should have these privileges by default. Otherwise, make sure to grant your user create privileges. You can learn how to do that in [PostgreSQL's documentation](https://www.postgresql.org/docs/current/ddl-priv.html). +- `[:password]`: an optional password for the user. When provided, make sure to put `:` before the password. +- `[host]`: (required) your PostgreSQL host. When run locally, it should be `localhost`. +- `[:port]`: an optional port that the PostgreSQL server is listening on. By default, it's `5432`. When provided, make sure to put `:` before the port. +- `[dbname]`: (required) the name of the database. -Medusa has three runtime modes: +You can learn more about the connection URL format in [PostgreSQL’s documentation](https://www.postgresql.org/docs/current/libpq-connect.html). -- Use `shared` to run the application in a single process. -- Use `worker` to run the a worker process only. -- Use `server` to run the application server only. +#### Example -In production, it's recommended to deploy two instances: +For example, set the following database URL in your environment variables: -1. One having the `workerMode` configuration set to `server`. -2. Another having the `workerMode` configuration set to `worker`. +```bash +DATABASE_URL=postgres://postgres@localhost/medusa-store +``` -#### Example +Then, use the value in `medusa-config.ts`: ```ts title="medusa-config.ts" module.exports = defineConfig({ projectConfig: { - workerMode: process.env.WORKER_MODE || "shared" + databaseUrl: process.env.DATABASE_URL, // ... }, // ... }) ``` -### http - -This property configures the application's http-specific settings. +### databaseSchema -#### Example +The database schema to connect to. This is not required to provide if you’re using the default schema, which is `public`. ```ts title="medusa-config.ts" module.exports = defineConfig({ projectConfig: { - http: { - cookieSecret: "supersecret", - compression: { - // ... - } - } + databaseSchema: process.env.DATABASE_SCHEMA || + "custom", // ... }, // ... }) ``` -#### Properties - -- authCors: (\`string\`) The Medusa application's API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes. - - \`cors\` is a string used to specify the accepted URLs or patterns for API Routes starting with \`/auth\`. It can either be one accepted origin, or a comma-separated list of accepted origins. - - Every origin in that list must either be: - - 1\. A URL. For example, \`http://localhost:7001\`. The URL must not end with a backslash; - 2\. Or a regular expression pattern that can match more than one origin. For example, \`.example.com\`. The regex pattern that Medusa tests for is \`^(\[/~@;%#'])(.\*?)\1(\[gimsuy]\*)$\`. -- storeCors: (\`string\`) The Medusa application's API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes. - - \`store\_cors\` is a string used to specify the accepted URLs or patterns for store API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins. - - Every origin in that list must either be: - - 1\. A URL. For example, \`http://localhost:8000\`. The URL must not end with a backslash; - 2\. Or a regular expression pattern that can match more than one origin. For example, \`.example.com\`. The regex pattern that the backend tests for is \`^(\[/~@;%#'])(.\*?)\1(\[gimsuy]\*)$\`. -- adminCors: (\`string\`) The Medusa application's API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes. - - \`admin\_cors\` is a string used to specify the accepted URLs or patterns for admin API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins. - - Every origin in that list must either be: - - 1\. A URL. For example, \`http://localhost:7001\`. The URL must not end with a backslash; - 2\. Or a regular expression pattern that can match more than one origin. For example, \`.example.com\`. The regex pattern that the backend tests for is \`^(\[/~@;%#'])(.\*?)\1(\[gimsuy]\*)$\`. -- jwtSecret: (\`string\`) A random string used to create authentication tokens in the http layer. Although this configuration option is not required, it’s highly recommended to set it for better security. - - In a development environment, if this option is not set the default secret is \`supersecret\`. However, in production, if this configuration is not set, an - error is thrown and the application crashes. -- jwtExpiresIn: (\`string\`) The expiration time for the JWT token. Its format is based off the \[ms package]\(https://github.com/vercel/ms). - - If not provided, the default value is \`24h\`. -- cookieSecret: (\`string\`) A random string used to create cookie tokens in the http layer. Although this configuration option is not required, it’s highly recommended to set it for better security. - - In a development environment, if this option is not set, the default secret is \`supersecret\`. However, in production, if this configuration is not set, an error is thrown and - the application crashes. -- compression: (\[HttpCompressionOptions]\(../medusa\_config.HttpCompressionOptions/page.mdx)) Configure HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there. - However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative. - - If you enable HTTP compression and you want to disable it for specific API Routes, you can pass in the request header \`"x-no-compression": true\`. - Learn more in the \[API Reference]\(https://docs.medusajs.com/api/store#http-compression). - - - enabled: (\`boolean\`) Whether HTTP compression is enabled. By default, it's \`false\`. - - - level: (\`number\`) The level of zlib compression to apply to responses. A higher level will result in better compression but will take longer to complete. - A lower level will result in less compression but will be much faster. The default value is \`6\`. - - - memLevel: (\`number\`) How much memory should be allocated to the internal compression state. It's an integer in the range of 1 (minimum level) and 9 (maximum level). - The default value is \`8\`. - - - threshold: (\`string\` \\| \`number\`) The minimum response body size that compression is applied on. Its value can be the number of bytes or any string accepted by the - \[bytes]\(https://www.npmjs.com/package/bytes) module. The default value is \`1024\`. -- authMethodsPerActor: (\`Record\\`) This configuration specifies the supported authentication providers per actor type (such as \`user\`, \`customer\`, or any custom actors). - For example, you only want to allow SSO logins for \`users\`, while you want to allow email/password logins for \`customers\` to the storefront. - - \`authMethodsPerActor\` is a a map where the actor type (eg. 'user') is the key, and the value is an array of supported auth provider IDs. -- restrictedFields: (\`object\`) Specifies the fields that can't be selected in the response unless specified in the allowed query config. - This is useful to restrict sensitive fields from being exposed in the API. - - - store: (\`string\`\[]) - -*** - -## admin +### databaseLogging -This property holds configurations for the Medusa Admin dashboard. +This configuration specifies whether database messages should be logged. -### Example +#### Example ```ts title="medusa-config.ts" module.exports = defineConfig({ - admin: { - backendUrl: process.env.MEDUSA_BACKEND_URL || - "http://localhost:9000" + projectConfig: { + databaseLogging: false + // ... }, // ... }) ``` -### disable +### databaseDriverOptions -Whether to disable the admin dashboard. If set to `true`, the admin dashboard is disabled, -in both development and production environments. The default value is `false`. +This configuration is used to pass additional options to the database connection. You can pass any configuration. For example, pass the +`ssl` property that enables support for TLS/SSL connections. + +This is useful for production databases, which can be supported by setting the `rejectUnauthorized` attribute of `ssl` object to `false`. +During development, it’s recommended not to pass this option. + +:::note + +Make sure to add to the end of the database URL `?ssl_mode=disable` as well when disabling `rejectUnauthorized`. + +::: #### Example ```ts title="medusa-config.ts" module.exports = defineConfig({ - admin: { - disable: process.env.ADMIN_DISABLED === "true" || - false + projectConfig: { + databaseDriverOptions: process.env.NODE_ENV !== "development" ? + { connection: { ssl: { rejectUnauthorized: false } } } : {} + // ... }, // ... }) ``` -### path +#### Properties -The path to the admin dashboard. The default value is `/app`. +- connection: (\`object\`) + + - ssl: (\`boolean\` \\| \`ConnectionOptions\`) Configure support for TLS/SSL connection -The value cannot be one of the reserved paths: +### redisUrl -- `/admin` -- `/store` -- `/auth` -- `/` +This configuration specifies the connection URL to Redis to store the Medusa server's session. :::note -When using Docker, make sure that the root path of the Docker image doesn't path the admin's `path`. For example, if the Docker image's root path is `/app`, change -the value of the `path` configuration, as it's `/app` by default. +You must first have Redis installed. You can refer to [Redis's installation guide](https://redis.io/docs/getting-started/installation/). ::: -#### Example +The Redis connection URL has the following format: -```ts title="medusa-config.ts" -module.exports = defineConfig({ - admin: { - path: process.env.ADMIN_PATH || `/app`, - }, - // ... -}) +```bash +redis[s]://[[username][:password]@][host][:port][/db-number] ``` -### outDir - -The directory where the admin build is outputted when you run the `build` command. -The default value is `./build`. +For a local Redis installation, the connection URL should be `redis://localhost:6379` unless you’ve made any changes to the Redis configuration during installation. #### Example ```ts title="medusa-config.ts" module.exports = defineConfig({ - admin: { - outDir: process.env.ADMIN_BUILD_DIR || `./build`, + projectConfig: { + redisUrl: process.env.REDIS_URL || + "redis://localhost:6379", + // ... }, // ... }) ``` -### backendUrl +### redisPrefix -The URL of your Medusa application. Defaults to the browser origin. This is useful to set when running the admin on a separate domain. +This configuration defines a prefix on all keys stored in Redis for the Medusa server's session. The default value is `sess:`. + +If this configuration option is provided, it is prepended to `sess:`. #### Example ```ts title="medusa-config.ts" module.exports = defineConfig({ - admin: { - backendUrl: process.env.MEDUSA_BACKEND_URL || - "http://localhost:9000" + projectConfig: { + redisPrefix: process.env.REDIS_URL || "medusa:", + // ... }, // ... }) ``` -### vite - -Configure the Vite configuration for the admin dashboard. This function receives the default Vite configuration -and returns the modified configuration. The default value is `undefined`. +### redisOptions -Learn about configurations you can pass to Vite in [Vite's documentation](https://vite.dev/config/). +This configuration defines options to pass ioredis for the Redis connection used to store the Medusa server's session. Refer to [ioredis’s RedisOptions documentation](https://redis.github.io/ioredis/index.html#RedisOptions) +for the list of available options. #### Example -For example, if you're using a third-party library that isn't ESM-compatible, add it to Vite's `optimizeDeps` configuration: - ```ts title="medusa-config.ts" module.exports = defineConfig({ - admin: { - vite: () => { - return { - optimizeDeps: { - include: ["qs"], - }, - }; - }, + projectConfig: { + redisOptions: { + connectionName: process.env.REDIS_CONNECTION_NAME || + "medusa", + } + // ... }, // ... }) ``` -*** - -## plugins - -On your Medusa server, you can use [Plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md) to add re-usable Medusa customizations. Plugins -can include modules, workflows, API Routes, and other customizations. Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0). - -Aside from installing the plugin with NPM, you need to pass the plugin you installed into the `plugins` array defined in `medusa-config.ts`. - -The items in the array can either be: - -- A string, which is the name of the plugin's package as specified in the plugin's `package.json` file. You can pass a plugin as a string if it doesn’t require any options. -- An object having the following properties: - - `resolve`: The name of the plugin's package as specified in the plugin's `package.json` file. - - `options`: An object that includes options to be passed to the modules within the plugin. Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md). +### sessionOptions -Learn how to create a plugin in [this documentation](https://docs.medusajs.com/learn/fundamentals/plugins/create/index.html.md). +This configuration defines additional options to pass to [express-session](https://www.npmjs.com/package/express-session), which is used to store the Medusa server's session. -### Example +#### Example ```ts title="medusa-config.ts" -module.exports = { - plugins: [ - `medusa-my-plugin-1`, - { - resolve: `medusa-my-plugin`, - options: { - apiKey: process.env.MY_API_KEY || - `test`, - }, - }, +module.exports = defineConfig({ + projectConfig: { + sessionOptions: { + name: process.env.SESSION_NAME || "custom", + } // ... - ], + }, // ... -} +}) ``` -### resolve - -The name of the plugin's package as specified in the plugin's `package.json` file. +#### Properties -### options +- name: (\`string\`) The name of the session ID cookie to set in the response (and read from in the request). The default value is \`connect.sid\`. + Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#name) for more details. +- resave: (\`boolean\`) Whether the session should be saved back to the session store, even if the session was never modified during the request. The default value is \`true\`. + Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#resave) for more details. +- rolling: (\`boolean\`) Whether the session identifier cookie should be force-set on every response. The default value is \`false\`. + Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#rolling) for more details. +- saveUninitialized: (\`boolean\`) Whether a session that is "uninitialized" is forced to be saved to the store. The default value is \`true\`. + Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#saveUninitialized) for more details. +- secret: (\`string\`) The secret to sign the session ID cookie. By default, the value of \`http.cookieSecret\` is used. + Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#secret) for details. +- ttl: (\`number\`) Used when calculating the \`Expires\` \`Set-Cookie\` attribute of cookies. By default, its value is \`10 \* 60 \* 60 \* 1000\`. + Refer to \[express-session’s documentation]\(https://www.npmjs.com/package/express-session#cookiemaxage) for details. -An object that includes options to be passed to the modules within the plugin. -Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md). +### workerMode -*** +Configure the application's worker mode. -## modules +Workers are processes running separately from the main application. They're useful for executing long-running or resource-heavy tasks in the background, such as importing products. -This property holds all custom modules installed in your Medusa application. +With a worker, these tasks are offloaded to a separate process. So, they won't affect the performance of the main application. -:::note +![Diagram showcasing how the server and worker work together](https://res.cloudinary.com/dza7lstvk/image/upload/fl_lossy/f_auto/r_16/ar_16:9,c_pad/v1/Medusa%20Book/medusa-worker_klkbch.jpg?_a=BATFJtAA0) -Medusa's commerce modules are configured by default, so only -add them to this property if you're changing their configurations or adding providers to a module. +Medusa has three runtime modes: -::: +- Use `shared` to run the application in a single process. +- Use `worker` to run the a worker process only. +- Use `server` to run the application server only. -`modules` is an array of objects, each holding a module's registration configurations. Each object has the following properties: +In production, it's recommended to deploy two instances: -1. `resolve`: a string indicating the path to the module relative to `src`, or the module's NPM package name. For example, `./modules/my-module`. -2. `options`: (optional) an object indicating the options to pass to the module. +1. One having the `workerMode` configuration set to `server`. +2. Another having the `workerMode` configuration set to `worker`. -### Example +#### Example ```ts title="medusa-config.ts" module.exports = defineConfig({ - modules: [ - { - resolve: "./modules/hello" - } - ] + projectConfig: { + workerMode: process.env.WORKER_MODE || "shared" + // ... + }, // ... }) ``` -*** - -## featureFlags - -Some features in the Medusa application are guarded by a feature flag. This ensures constant shipping of new features while maintaining the engine’s stability. - -You can enable a feature in your application by enabling its feature flag. Feature flags are enabled through either environment -variables or through this configuration property exported in `medusa-config.ts`. - -The `featureFlags`'s value is an object. Its properties are the names of the feature flags, and their value is a boolean indicating whether the feature flag is enabled. +### http -You can find available feature flags and their key name [here](https://github.com/medusajs/medusa/tree/develop/packages/medusa/src/loaders/feature-flags). +This property configures the application's http-specific settings. -### Example +#### Example ```ts title="medusa-config.ts" module.exports = defineConfig({ - featureFlags: { - analytics: true, + projectConfig: { + http: { + cookieSecret: "supersecret", + compression: { + // ... + } + } // ... - } + }, // ... }) ``` -:::note - -After enabling a feature flag, make sure to run migrations as it may require making changes to the database. - -::: - - -# Admin Components +#### Properties -In this section, you'll find examples of implementing common Medusa Admin components and layouts. +- authCors: (\`string\`) The Medusa application's API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes. -These components are useful to follow the same design conventions as the Medusa Admin, and are build on top of the [Medusa UI package](https://docs.medusajs.com/ui/index.html.md). + \`cors\` is a string used to specify the accepted URLs or patterns for API Routes starting with \`/auth\`. It can either be one accepted origin, or a comma-separated list of accepted origins. -Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.md) for a full list of components. + Every origin in that list must either be: -## Layouts + 1\. A URL. For example, \`http://localhost:7001\`. The URL must not end with a backslash; + 2\. Or a regular expression pattern that can match more than one origin. For example, \`.example.com\`. The regex pattern that Medusa tests for is \`^(\[/~@;%#'])(.\*?)\1(\[gimsuy]\*)$\`. +- storeCors: (\`string\`) The Medusa application's API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes. -Use these components to set the layout of your UI route. + \`store\_cors\` is a string used to specify the accepted URLs or patterns for store API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins. -*** + Every origin in that list must either be: -## Components + 1\. A URL. For example, \`http://localhost:8000\`. The URL must not end with a backslash; + 2\. Or a regular expression pattern that can match more than one origin. For example, \`.example.com\`. The regex pattern that the backend tests for is \`^(\[/~@;%#'])(.\*?)\1(\[gimsuy]\*)$\`. +- adminCors: (\`string\`) The Medusa application's API Routes are protected by Cross-Origin Resource Sharing (CORS). So, only allowed URLs or URLs matching a specified pattern can send requests to the backend’s API Routes. -Use these components in your widgets and UI routes. + \`admin\_cors\` is a string used to specify the accepted URLs or patterns for admin API Routes. It can either be one accepted origin, or a comma-separated list of accepted origins. + Every origin in that list must either be: -# Action Menu - Admin Components + 1\. A URL. For example, \`http://localhost:7001\`. The URL must not end with a backslash; + 2\. Or a regular expression pattern that can match more than one origin. For example, \`.example.com\`. The regex pattern that the backend tests for is \`^(\[/~@;%#'])(.\*?)\1(\[gimsuy]\*)$\`. +- jwtSecret: (\`string\`) A random string used to create authentication tokens in the http layer. Although this configuration option is not required, it’s highly recommended to set it for better security. -The Medusa Admin often provides additional actions in a dropdown shown when users click a three-dot icon. + In a development environment, if this option is not set the default secret is \`supersecret\`. However, in production, if this configuration is not set, an + error is thrown and the application crashes. +- jwtExpiresIn: (\`string\`) The expiration time for the JWT token. Its format is based off the \[ms package]\(https://github.com/vercel/ms). -![Example of an action menu in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728291319/Medusa%20Resources/action-menu_jnus6k.png) + If not provided, the default value is \`24h\`. +- cookieSecret: (\`string\`) A random string used to create cookie tokens in the http layer. Although this configuration option is not required, it’s highly recommended to set it for better security. -To create a component that shows this menu in your customizations, create the file `src/admin/components/action-menu.tsx` with the following content: + In a development environment, if this option is not set, the default secret is \`supersecret\`. However, in production, if this configuration is not set, an error is thrown and + the application crashes. +- compression: (\[HttpCompressionOptions]\(../medusa\_config.HttpCompressionOptions/page.mdx)) Configure HTTP compression from the application layer. If you have access to the HTTP server, the recommended approach would be to enable it there. + However, some platforms don't offer access to the HTTP layer and in those cases, this is a good alternative. -```tsx title="src/admin/components/action-menu.tsx" -import { - DropdownMenu, - IconButton, - clx, -} from "@medusajs/ui" -import { EllipsisHorizontal } from "@medusajs/icons" -import { Link } from "react-router-dom" + If you enable HTTP compression and you want to disable it for specific API Routes, you can pass in the request header \`"x-no-compression": true\`. + Learn more in the \[API Reference]\(https://docs.medusajs.com/api/store#http-compression). -export type Action = { - icon: React.ReactNode - label: string - disabled?: boolean -} & ( - | { - to: string - onClick?: never - } - | { - onClick: () => void - to?: never - } -) + - enabled: (\`boolean\`) Whether HTTP compression is enabled. By default, it's \`false\`. -export type ActionGroup = { - actions: Action[] -} + - level: (\`number\`) The level of zlib compression to apply to responses. A higher level will result in better compression but will take longer to complete. + A lower level will result in less compression but will be much faster. The default value is \`6\`. -export type ActionMenuProps = { - groups: ActionGroup[] -} + - memLevel: (\`number\`) How much memory should be allocated to the internal compression state. It's an integer in the range of 1 (minimum level) and 9 (maximum level). + The default value is \`8\`. -export const ActionMenu = ({ groups }: ActionMenuProps) => { - return ( - - - - - - - - {groups.map((group, index) => { - if (!group.actions.length) { - return null - } + - threshold: (\`string\` \\| \`number\`) The minimum response body size that compression is applied on. Its value can be the number of bytes or any string accepted by the + \[bytes]\(https://www.npmjs.com/package/bytes) module. The default value is \`1024\`. +- authMethodsPerActor: (\`Record\\`) This configuration specifies the supported authentication providers per actor type (such as \`user\`, \`customer\`, or any custom actors). + For example, you only want to allow SSO logins for \`users\`, while you want to allow email/password logins for \`customers\` to the storefront. - const isLast = index === groups.length - 1 + \`authMethodsPerActor\` is a a map where the actor type (eg. 'user') is the key, and the value is an array of supported auth provider IDs. +- restrictedFields: (\`object\`) Specifies the fields that can't be selected in the response unless specified in the allowed query config. + This is useful to restrict sensitive fields from being exposed in the API. - return ( - - {group.actions.map((action, index) => { - if (action.onClick) { - return ( - { - e.stopPropagation() - action.onClick() - }} - className={clx( - "[&_svg]:text-ui-fg-subtle flex items-center gap-x-2", - { - "[&_svg]:text-ui-fg-disabled": action.disabled, - } - )} - > - {action.icon} - {action.label} - - ) - } + - store: (\`string\`\[]) - return ( -
- - e.stopPropagation()}> - {action.icon} - {action.label} - - -
- ) - })} - {!isLast && } -
- ) - })} -
-
- ) -} -``` +*** -The `ActionMenu` component shows a three-dots icon (or `EllipsisHorizontal`) from the [Medusa Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md) in a button. +## admin -When the button is clicked, a dropdown menu is shown with the actions passed in the props. +This property holds configurations for the Medusa Admin dashboard. -The component accepts the following props: +### Example -- groups: (\`object\[]\`) Groups of actions to be shown in the dropdown. Each group is separated by a divider. +```ts title="medusa-config.ts" +module.exports = defineConfig({ + admin: { + backendUrl: process.env.MEDUSA_BACKEND_URL || + "http://localhost:9000" + }, + // ... +}) +``` - - actions: (\`object\[]\`) Actions in the group. +### disable - - icon: (\`React.ReactNode\`) +Whether to disable the admin dashboard. If set to `true`, the admin dashboard is disabled, +in both development and production environments. The default value is `false`. - - label: (\`string\`) The action's text. +#### Example - - disabled: (\`boolean\`) Whether the action is shown as disabled. +```ts title="medusa-config.ts" +module.exports = defineConfig({ + admin: { + disable: process.env.ADMIN_DISABLED === "true" || + false + }, + // ... +}) +``` - - \`to\`: (\`string\`) The link to take the user to when they click the action. This is required if \`onClick\` isn't provided. +### path - - \`onClick\`: (\`() => void\`) The function to execute when the action is clicked. This is required if \`to\` isn't provided. +The path to the admin dashboard. The default value is `/app`. -*** +The value cannot be one of the reserved paths: -## Example +- `/admin` +- `/store` +- `/auth` +- `/` -Use the `ActionMenu` component in any widget or UI route. +:::note -For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: +When using Docker, make sure that the root path of the Docker image doesn't path the admin's `path`. For example, if the Docker image's root path is `/app`, change +the value of the `path` configuration, as it's `/app` by default. -```tsx title="src/admin/widgets/product-widget.tsx" -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { Pencil } from "@medusajs/icons" -import { Container } from "../components/container" -import { ActionMenu } from "../components/action-menu" +::: -const ProductWidget = () => { - return ( - - , - label: "Edit", - onClick: () => { - alert("You clicked the edit action!") - }, - }, - ], - }, - ]} /> - - ) -} +#### Example -export const config = defineWidgetConfig({ - zone: "product.details.before", +```ts title="medusa-config.ts" +module.exports = defineConfig({ + admin: { + path: process.env.ADMIN_PATH || `/app`, + }, + // ... }) +``` -export default ProductWidget +### outDir + +The directory where the admin build is outputted when you run the `build` command. +The default value is `./build`. + +#### Example + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + admin: { + outDir: process.env.ADMIN_BUILD_DIR || `./build`, + }, + // ... +}) ``` -This widget also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) custom component. +### backendUrl -### Use in Header +The URL of your Medusa application. Defaults to the browser origin. This is useful to set when running the admin on a separate domain. -You can also use the action menu in the [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) component as part of its actions. +#### Example -For example: +```ts title="medusa-config.ts" +module.exports = defineConfig({ + admin: { + backendUrl: process.env.MEDUSA_BACKEND_URL || + "http://localhost:9000" + }, + // ... +}) +``` -```tsx title="src/admin/widgets/product-widget.tsx" -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { Pencil } from "@medusajs/icons" -import { Container } from "../components/container" -import { Header } from "../components/header" +### storefrontUrl -const ProductWidget = () => { - return ( - -
, - label: "Edit", - onClick: () => { - alert("You clicked the edit action!") - }, - }, - ], - }, - ], - }, - }, - ]} - /> - - ) -} +The URL of your Medusa storefront application. This URL is used as a prefix to some +links in the admin that require performing actions in the storefront. For example, +this URL is used as a prefix to shareable payment links for orders with +outstanding amounts. -export const config = defineWidgetConfig({ - zone: "product.details.before", +#### Example + +```js title="medusa-config.js" +module.exports = defineConfig({ + admin: { + storefrontUrl: process.env.MEDUSA_STOREFRONT_URL || + "http://localhost:8000" + }, + // ... }) +``` + +### vite + +Configure the Vite configuration for the admin dashboard. This function receives the default Vite configuration +and returns the modified configuration. The default value is `undefined`. + +Learn about configurations you can pass to Vite in [Vite's documentation](https://vite.dev/config/). + +#### Example + +For example, if you're using a third-party library that isn't ESM-compatible, add it to Vite's `optimizeDeps` configuration: -export default ProductWidget +```ts title="medusa-config.ts" +module.exports = defineConfig({ + admin: { + vite: () => { + return { + optimizeDeps: { + include: ["qs"], + }, + }; + }, + }, + // ... +}) ``` +*** -# Container - Admin Components +## plugins -The Medusa Admin wraps each section of a page in a container. +On your Medusa server, you can use [Plugins](https://docs.medusajs.com/learn/fundamentals/plugins/index.html.md) to add re-usable Medusa customizations. Plugins +can include modules, workflows, API Routes, and other customizations. Plugins are available starting from [Medusa v2.3.0](https://github.com/medusajs/medusa/releases/tag/v2.3.0). -![Example of a container in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728287102/Medusa%20Resources/container_soenir.png) +Aside from installing the plugin with NPM, you need to pass the plugin you installed into the `plugins` array defined in `medusa-config.ts`. -To create a component that uses the same container styling in your widgets or UI routes, create the file `src/admin/components/container.tsx` with the following content: +The items in the array can either be: -```tsx -import { - Container as UiContainer, - clx, -} from "@medusajs/ui" +- A string, which is the name of the plugin's package as specified in the plugin's `package.json` file. You can pass a plugin as a string if it doesn’t require any options. +- An object having the following properties: + - `resolve`: The name of the plugin's package as specified in the plugin's `package.json` file. + - `options`: An object that includes options to be passed to the modules within the plugin. Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md). -type ContainerProps = React.ComponentProps +Learn how to create a plugin in [this documentation](https://docs.medusajs.com/learn/fundamentals/plugins/create/index.html.md). -export const Container = (props: ContainerProps) => { - return ( - - ) +### Example + +```ts title="medusa-config.ts" +module.exports = { + plugins: [ + `medusa-my-plugin-1`, + { + resolve: `medusa-my-plugin`, + options: { + apiKey: process.env.MY_API_KEY || + `test`, + }, + }, + // ... + ], + // ... } ``` -The `Container` component re-uses the component from the [Medusa UI package](https://docs.medusajs.com/ui/components/container/index.html.md) and applies to it classes to match the Medusa Admin's design conventions. - -*** +### resolve -## Example +The name of the plugin's package as specified in the plugin's `package.json` file. -Use that `Container` component in any widget or UI route. +### options -For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: +An object that includes options to be passed to the modules within the plugin. +Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/modules/options/index.html.md). -```tsx title="src/admin/widgets/product-widget.tsx" -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { Container } from "../components/container" -import { Header } from "../components/header" +*** -const ProductWidget = () => { - return ( - -
- - ) -} +## modules -export const config = defineWidgetConfig({ - zone: "product.details.before", -}) +This property holds all custom modules installed in your Medusa application. -export default ProductWidget -``` +:::note -This widget also uses a [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component. +Medusa's commerce modules are configured by default, so only +add them to this property if you're changing their configurations or adding providers to a module. +::: -# JSON View - Admin Components +`modules` is an array of objects, each holding a module's registration configurations. Each object has the following properties: -Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format. +1. `resolve`: a string indicating the path to the module relative to `src`, or the module's NPM package name. For example, `./modules/my-module`. +2. `options`: (optional) an object indicating the options to pass to the module. -![Example of a JSON section in the admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728295129/Medusa%20Resources/json_dtbsgm.png) +### Example -To create a component that shows a JSON section in your customizations, create the file `src/admin/components/json-view-section.tsx` with the following content: +```ts title="medusa-config.ts" +module.exports = defineConfig({ + modules: [ + { + resolve: "./modules/hello" + } + ] + // ... +}) +``` -```tsx title="src/admin/components/json-view-section.tsx" -import { - ArrowUpRightOnBox, - Check, - SquareTwoStack, - TriangleDownMini, - XMarkMini, -} from "@medusajs/icons" -import { - Badge, - Container, - Drawer, - Heading, - IconButton, - Kbd, -} from "@medusajs/ui" -import Primitive from "@uiw/react-json-view" -import { CSSProperties, MouseEvent, Suspense, useState } from "react" +*** -type JsonViewSectionProps = { - data: object - title?: string -} +## featureFlags -export const JsonViewSection = ({ data }: JsonViewSectionProps) => { - const numberOfKeys = Object.keys(data).length +Some features in the Medusa application are guarded by a feature flag. This ensures constant shipping of new features while maintaining the engine’s stability. - return ( - -
- JSON - - {numberOfKeys} keys - -
- - - - - - - -
-
- - - - {numberOfKeys} - - - -
-
- - esc - - - - - - -
-
- -
-
} - > - - } /> - ( - null - )} - /> - ( - undefined - )} - /> - { - return ( - - {Object.keys(value as object).length} items - - ) - }} - /> - - - - - : - - { - return - }} - /> - - - -
-
-
-
+You can enable a feature in your application by enabling its feature flag. Feature flags are enabled through either environment +variables or through this configuration property exported in `medusa-config.ts`. + +The `featureFlags`'s value is an object. Its properties are the names of the feature flags, and their value is a boolean indicating whether the feature flag is enabled. + +You can find available feature flags and their key name [here](https://github.com/medusajs/medusa/tree/develop/packages/medusa/src/loaders/feature-flags). + +### Example + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + featureFlags: { + analytics: true, + // ... + } + // ... +}) +``` + +:::note + +After enabling a feature flag, make sure to run migrations as it may require making changes to the database. + +::: + + +# Admin Components + +In this section, you'll find examples of implementing common Medusa Admin components and layouts. + +These components are useful to follow the same design conventions as the Medusa Admin, and are build on top of the [Medusa UI package](https://docs.medusajs.com/ui/index.html.md). + +Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/index.html.md) for a full list of components. + +## Layouts + +Use these components to set the layout of your UI route. + +*** + +## Components + +Use these components in your widgets and UI routes. + + +# Single Column Layout - Admin Components + +The Medusa Admin has pages with a single column of content. + +This doesn't include the sidebar, only the main content. + +![An example of an admin page with a single column](https://res.cloudinary.com/dza7lstvk/image/upload/v1728286605/Medusa%20Resources/single-column.png) + +To create a layout that you can use in UI routes to support one column of content, create the component `src/admin/layouts/single-column.tsx` with the following content: + +```tsx title="src/admin/layouts/single-column.tsx" +export type SingleColumnLayoutProps = { + children: React.ReactNode +} + +export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => { + return ( +
+ {children} +
) } +``` -type CopiedProps = { - style?: CSSProperties - value: object | undefined +The `SingleColumnLayout` accepts the content in the `children` props. + +*** + +## Example + +Use the `SingleColumnLayout` component in your UI routes that have a single column. For example: + +```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]} +import { defineRouteConfig } from "@medusajs/admin-sdk" +import { ChatBubbleLeftRight } from "@medusajs/icons" +import { Container } from "../../components/container" +import { SingleColumnLayout } from "../../layouts/single-column" +import { Header } from "../../components/header" + +const CustomPage = () => { + return ( + + +
+ + + ) } -const Copied = ({ style, value }: CopiedProps) => { - const [copied, setCopied] = useState(false) +export const config = defineRouteConfig({ + label: "Custom", + icon: ChatBubbleLeftRight, +}) - const handler = (e: MouseEvent) => { - e.stopPropagation() - setCopied(true) +export default CustomPage +``` - if (typeof value === "string") { - navigator.clipboard.writeText(value) - } else { - const json = JSON.stringify(value, null, 2) - navigator.clipboard.writeText(json) - } +This UI route also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and a [Header]() custom components. - setTimeout(() => { - setCopied(false) - }, 2000) - } - const styl = { whiteSpace: "nowrap", width: "20px" } +# Two Column Layout - Admin Components - if (copied) { - return ( - - - - ) - } +The Medusa Admin has pages with two columns of content. + +This doesn't include the sidebar, only the main content. + +![An example of an admin page with two columns](https://res.cloudinary.com/dza7lstvk/image/upload/v1728286690/Medusa%20Resources/two-column_sdnkg0.png) + +To create a layout that you can use in UI routes to support two columns of content, create the component `src/admin/layouts/two-column.tsx` with the following content: +```tsx title="src/admin/layouts/two-column.tsx" +export type TwoColumnLayoutProps = { + firstCol: React.ReactNode + secondCol: React.ReactNode +} + +export const TwoColumnLayout = ({ + firstCol, + secondCol, +}: TwoColumnLayoutProps) => { return ( - - - +
+
+ {firstCol} +
+
+ {secondCol} +
+
) } ``` -The `JsonViewSection` component shows a section with the "JSON" title and a button to show the data as JSON in a drawer or side window. +The `TwoColumnLayout` accepts two props: -The `JsonViewSection` accepts a `data` prop, which is the data to show as a JSON object in the drawer. +- `firstCol` indicating the content of the first column. +- `secondCol` indicating the content of the second column. *** ## Example -Use the `JsonViewSection` component in any widget or UI route. - -For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: +Use the `TwoColumnLayout` component in your UI routes that have a single column. For example: -```tsx title="src/admin/widgets/product-widget.tsx" -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { JsonViewSection } from "../components/json-view-section" +```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]} +import { defineRouteConfig } from "@medusajs/admin-sdk" +import { ChatBubbleLeftRight } from "@medusajs/icons" +import { Container } from "../../components/container" +import { Header } from "../../components/header" +import { TwoColumnLayout } from "../../layouts/two-column" -const ProductWidget = () => { - return +const CustomPage = () => { + return ( + +
+ + } + secondCol={ + +
+ + } + /> + ) } -export const config = defineWidgetConfig({ - zone: "product.details.before", +export const config = defineRouteConfig({ + label: "Custom", + icon: ChatBubbleLeftRight, }) -export default ProductWidget +export default CustomPage ``` -This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`. +This UI route also uses [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header]() custom components. -# Header - Admin Components +# Action Menu - Admin Components -Each section in the Medusa Admin has a header with a title, and optionally a subtitle with buttons to perform an action. +The Medusa Admin often provides additional actions in a dropdown shown when users click a three-dot icon. -![Example of a header in a section](https://res.cloudinary.com/dza7lstvk/image/upload/v1728288562/Medusa%20Resources/header_dtz4gl.png) +![Example of an action menu in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728291319/Medusa%20Resources/action-menu_jnus6k.png) -To create a component that uses the same header styling and structure, create the file `src/admin/components/header.tsx` with the following content: +To create a component that shows this menu in your customizations, create the file `src/admin/components/action-menu.tsx` with the following content: -```tsx title="src/admin/components/header.tsx" -import { Heading, Button, Text } from "@medusajs/ui" -import React from "react" -import { Link, LinkProps } from "react-router-dom" -import { ActionMenu, ActionMenuProps } from "./action-menu" +```tsx title="src/admin/components/action-menu.tsx" +import { + DropdownMenu, + IconButton, + clx, +} from "@medusajs/ui" +import { EllipsisHorizontal } from "@medusajs/icons" +import { Link } from "react-router-dom" -export type HeadingProps = { - title: string - subtitle?: string - actions?: ( - { - type: "button", - props: React.ComponentProps - link?: LinkProps - } | - { - type: "action-menu" - props: ActionMenuProps - } | - { - type: "custom" - children: React.ReactNode +export type Action = { + icon: React.ReactNode + label: string + disabled?: boolean +} & ( + | { + to: string + onClick?: never } - )[] + | { + onClick: () => void + to?: never + } +) + +export type ActionGroup = { + actions: Action[] } -export const Header = ({ - title, - subtitle, - actions = [], -}: HeadingProps) => { +export type ActionMenuProps = { + groups: ActionGroup[] +} + +export const ActionMenu = ({ groups }: ActionMenuProps) => { return ( -
-
- {title} - {subtitle && ( - - {subtitle} - - )} -
- {actions.length > 0 && ( -
- {actions.map((action, index) => ( - <> - {action.type === "button" && ( - - )} - {action.type === "action-menu" && ( - - )} - {action.type === "custom" && action.children} - - ))} -
- )} -
+ + + + + + + + {groups.map((group, index) => { + if (!group.actions.length) { + return null + } + + const isLast = index === groups.length - 1 + + return ( + + {group.actions.map((action, index) => { + if (action.onClick) { + return ( + { + e.stopPropagation() + action.onClick() + }} + className={clx( + "[&_svg]:text-ui-fg-subtle flex items-center gap-x-2", + { + "[&_svg]:text-ui-fg-disabled": action.disabled, + } + )} + > + {action.icon} + {action.label} + + ) + } + + return ( +
+ + e.stopPropagation()}> + {action.icon} + {action.label} + + +
+ ) + })} + {!isLast && } +
+ ) + })} +
+
) } ``` -The `Header` component shows a title, and optionally a subtitle and action buttons. +The `ActionMenu` component shows a three-dots icon (or `EllipsisHorizontal`) from the [Medusa Icons package](https://docs.medusajs.com/ui/icons/overview/index.html.md) in a button. -The component also uses the [Action Menu](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/action-menu/index.html.md) custom component. +When the button is clicked, a dropdown menu is shown with the actions passed in the props. -It accepts the following props: +The component accepts the following props: -- title: (\`string\`) The section's title. -- subtitle: (\`string\`) The section's subtitle. -- actions: (\`object\[]\`) An array of actions to show. +- groups: (\`object\[]\`) Groups of actions to be shown in the dropdown. Each group is separated by a divider. - - type: (\`button\` \\| \`action-menu\` \\| \`custom\`) The type of action to add. + - actions: (\`object\[]\`) Actions in the group. - \- If its value is \`button\`, it'll show a button that can have a link or an on-click action. + - icon: (\`React.ReactNode\`) - \- If its value is \`action-menu\`, it'll show a three dot icon with a dropdown of actions. + - label: (\`string\`) The action's text. - \- If its value is \`custom\`, you can pass any React nodes to render. + - disabled: (\`boolean\`) Whether the action is shown as disabled. - - props: (object) + - \`to\`: (\`string\`) The link to take the user to when they click the action. This is required if \`onClick\` isn't provided. - - children: (React.ReactNode) This property is only accepted if \`type\` is \`custom\`. Its content is rendered as part of the actions. + - \`onClick\`: (\`() => void\`) The function to execute when the action is clicked. This is required if \`to\` isn't provided. *** ## Example -Use the `Header` component in any widget or UI route. +Use the `ActionMenu` component in any widget or UI route. For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: ```tsx title="src/admin/widgets/product-widget.tsx" import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Pencil } from "@medusajs/icons" import { Container } from "../components/container" -import { Header } from "../components/header" +import { ActionMenu } from "../components/action-menu" const ProductWidget = () => { return ( -
, + label: "Edit", onClick: () => { - alert("You clicked the button.") + alert("You clicked the edit action!") }, }, - }, - ]} - /> + ], + }, + ]} /> ) } @@ -28938,85 +28627,45 @@ export default ProductWidget This widget also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) custom component. +### Use in Header -# Section Row - Admin Components - -The Medusa Admin often shows information in rows of label-values, such as when showing a product's details. - -![Example of a section row in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728292781/Medusa%20Resources/section-row_kknbnw.png) - -To create a component that shows information in the same structure, create the file `src/admin/components/section-row.tsx` with the following content: - -```tsx title="src/admin/components/section-row.tsx" -import { Text, clx } from "@medusajs/ui" - -export type SectionRowProps = { - title: string - value?: React.ReactNode | string | null - actions?: React.ReactNode -} - -export const SectionRow = ({ title, value, actions }: SectionRowProps) => { - const isValueString = typeof value === "string" || !value - - return ( -
- - {title} - - - {isValueString ? ( - - {value ?? "-"} - - ) : ( -
{value}
- )} - - {actions &&
{actions}
} -
- ) -} -``` - -The `SectionRow` component shows a title and a value in the same row. - -It accepts the following props: - -- title: (\`string\`) The title to show on the left side. -- value: (\`React.ReactNode\` \\| \`string\` \\| \`null\`) The value to show on the right side. -- actions: (\`React.ReactNode\`) The actions to show at the end of the row. - -*** - -## Example - -Use the `SectionRow` component in any widget or UI route. +You can also use the action menu in the [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) component as part of its actions. -For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: +For example: ```tsx title="src/admin/widgets/product-widget.tsx" import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Pencil } from "@medusajs/icons" import { Container } from "../components/container" import { Header } from "../components/header" -import { SectionRow } from "../components/section-row" const ProductWidget = () => { return ( -
- +
, + label: "Edit", + onClick: () => { + alert("You clicked the edit action!") + }, + }, + ], + }, + ], + }, + }, + ]} + /> ) } @@ -29028,294 +28677,461 @@ export const config = defineWidgetConfig({ export default ProductWidget ``` -This widget also uses the [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component. +# Data Table - Admin Components + +This component is available after [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0). + +The [DataTable component in Medusa UI](https://docs.medusajs.com/ui/components/data-table/index.html.md) allows you to display data in a table with sorting, filtering, and pagination. + +You can use this component in your Admin Extensions to display data in a table format, especially if they're retrieved from API routes of the Medusa application. -# Single Column Layout - Admin Components +Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/components/data-table/index.html.md) for detailed information about the DataTable component and its different usages. -The Medusa Admin has pages with a single column of content. +## Example: DataTable with Data Fetching -This doesn't include the sidebar, only the main content. +In this example, you'll create a UI widget that shows the list of products retrieved from the [List Products API Route](https://docs.medusajs.com/api/admin#products_getproducts) in a data table with pagination, filtering, searching, and sorting. -![An example of an admin page with a single column](https://res.cloudinary.com/dza7lstvk/image/upload/v1728286605/Medusa%20Resources/single-column.png) +Start by initializing the columns in the data table. To do that, use the `createDataTableColumnHelper` from Medusa UI: -To create a layout that you can use in UI routes to support one column of content, create the component `src/admin/layouts/single-column.tsx` with the following content: +```tsx title="src/admin/routes/custom/page.tsx" +import { + createDataTableColumnHelper, +} from "@medusajs/ui" +import { + HttpTypes, +} from "@medusajs/framework/types" -```tsx title="src/admin/layouts/single-column.tsx" -export type SingleColumnLayoutProps = { - children: React.ReactNode -} +const columnHelper = createDataTableColumnHelper() -export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => { - return ( -
- {children} -
- ) -} +const columns = [ + columnHelper.accessor("title", { + header: "Title", + // Enables sorting for the column. + enableSorting: true, + // If omitted, the header will be used instead if it's a string, + // otherwise the accessor key (id) will be used. + sortLabel: "Title", + // If omitted the default value will be "A-Z" + sortAscLabel: "A-Z", + // If omitted the default value will be "Z-A" + sortDescLabel: "Z-A", + }), + columnHelper.accessor("status", { + header: "Status", + cell: ({ getValue }) => { + const status = getValue() + return ( + + {status === "published" ? "Published" : "Draft"} + + ) + }, + }), +] ``` -The `SingleColumnLayout` accepts the content in the `children` props. - -*** +`createDataTableColumnHelper` utility creates a column helper that helps you define the columns for the data table. The column helper has an `accessor` method that accepts two parameters: -## Example +1. The column's key in the table's data. +2. An object with the following properties: + - `header`: The column's header. + - `cell`: (optional) By default, a data's value for a column is displayed as a string. Use this property to specify custom rendering of the value. It accepts a function that returns a string or a React node. The function receives an object that has a `getValue` property function to retrieve the raw value of the cell. + - `enableSorting`: (optional) A boolean that enables sorting data by this column. + - `sortLabel`: (optional) The label for the sorting button. If omitted, the `header` will be used instead if it's a string, otherwise the accessor key (id) will be used. + - `sortAscLabel`: (optional) The label for the ascending sorting button. If omitted, the default value will be "A-Z". + - `sortDescLabel`: (optional) The label for the descending sorting button. If omitted, the default value will be "Z-A". -Use the `SingleColumnLayout` component in your UI routes that have a single column. For example: +Next, you'll define the filters that can be applied to the data table. You'll configure filtering by product status. -```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]} -import { defineRouteConfig } from "@medusajs/admin-sdk" -import { ChatBubbleLeftRight } from "@medusajs/icons" -import { Container } from "../../components/container" -import { SingleColumnLayout } from "../../layouts/single-column" -import { Header } from "../../components/header" +To define the filters, add the following: -const CustomPage = () => { - return ( - - -
- - - ) -} +```tsx title="src/admin/routes/custom/page.tsx" +// other imports... +import { + // ... + createDataTableFilterHelper, +} from "@medusajs/ui" -export const config = defineRouteConfig({ - label: "Custom", - icon: ChatBubbleLeftRight, -}) +const filterHelper = createDataTableFilterHelper() -export default CustomPage +const filters = [ + filterHelper.accessor("status", { + type: "select", + label: "Status", + options: [ + { + label: "Published", + value: "published", + }, + { + label: "Draft", + value: "draft", + }, + ], + }), +] ``` -This UI route also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and a [Header]() custom components. +`createDataTableFilterHelper` utility creates a filter helper that helps you define the filters for the data table. The filter helper has an `accessor` method that accepts two parameters: +1. The key of a column in the table's data. +2. An object with the following properties: + - `type`: The type of filter. It can be either: + - `select`: A select dropdown allowing users to choose multiple values. + - `radio`: A radio button allowing users to choose one value. + - `date`: A date picker allowing users to choose a date. + - `label`: The filter's label. + - `options`: An array of objects with `label` and `value` properties. The `label` is the option's label, and the `value` is the value to filter by. -# Table - Admin Components +You'll now start creating the UI widget's component. Start by adding the necessary state variables: -If you're using [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0), it's recommended to use the [Data Table](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/data-table/index.html.md) component instead. +```tsx title="src/admin/routes/custom/page.tsx" +// other imports... +import { + // ... + DataTablePaginationState, + DataTableFilteringState, + DataTableSortingState, +} from "@medusajs/ui" +import { useMemo, useState } from "react" -The listing pages in the Admin show a table with pagination. +// ... -![Example of a table in the product listing page](https://res.cloudinary.com/dza7lstvk/image/upload/v1728295658/Medusa%20Resources/list_ddt9zc.png) +const limit = 15 -To create a component that shows a table with pagination, create the file `src/admin/components/table.tsx` with the following content: +const CustomPage = () => { + const [pagination, setPagination] = useState({ + pageSize: limit, + pageIndex: 0, + }) + const [search, setSearch] = useState("") + const [filtering, setFiltering] = useState({}) + const [sorting, setSorting] = useState(null) -```tsx title="src/admin/components/table.tsx" -import { useMemo } from "react" -import { Table as UiTable } from "@medusajs/ui" + const offset = useMemo(() => { + return pagination.pageIndex * limit + }, [pagination]) + const statusFilters = useMemo(() => { + return (filtering.status || []) as ProductStatus + }, [filtering]) -export type TableProps = { - columns: { - key: string - label?: string - render?: (value: unknown) => React.ReactNode - }[] - data: Record[] - pageSize: number - count: number - currentPage: number - setCurrentPage: (value: number) => void + // TODO add data fetching logic } +``` -export const Table = ({ - columns, - data, - pageSize, - count, - currentPage, - setCurrentPage, -}: TableProps) => { - const pageCount = useMemo(() => { - return Math.ceil(count / pageSize) - }, [data, pageSize]) +In the component, you've added the following state variables: - const canNextPage = useMemo(() => { - return currentPage < pageCount - 1 - }, [currentPage, pageCount]) - const canPreviousPage = useMemo(() => { - return currentPage - 1 >= 0 - }, [currentPage]) +- `pagination`: An object of type `DataTablePaginationState` that holds the pagination state. It has two properties: + - `pageSize`: The number of items to show per page. + - `pageIndex`: The current page index. +- `search`: A string that holds the search query. +- `filtering`: An object of type `DataTableFilteringState` that holds the filtering state. +- `sorting`: An object of type `DataTableSortingState` that holds the sorting state. - const nextPage = () => { - if (canNextPage) { - setCurrentPage(currentPage + 1) - } - } +You've also added two memoized variables: - const previousPage = () => { - if (canPreviousPage) { - setCurrentPage(currentPage - 1) - } - } +- `offset`: How many items to skip when fetching data based on the current page. +- `statusFilters`: The selected status filters, if any. - return ( -
- - - - {columns.map((column, index) => ( - - {column.label || column.key} - - ))} - - - - {data.map((item, index) => { - const rowIndex = "id" in item ? item.id as string : index - return ( - - {columns.map((column, index) => ( - - <> - {column.render && column.render(item[column.key])} - {!column.render && ( - <>{item[column.key] as string} - )} - - - ))} - - ) - })} - - - -
- ) -} +Next, you'll fetch the products from the Medusa application. Assuming you have the JS SDK configured as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), add the following imports at the top of the file: + +```tsx title="src/admin/routes/custom/page.tsx" +import { sdk } from "../../lib/config" +import { useQuery } from "@tanstack/react-query" ``` -The `Table` component uses the component from the [UI package](https://docs.medusajs.com/ui/components/table/index.html.md), with additional styling and rendering of data. +This imports the JS SDK instance and `useQuery` from [Tanstack Query](https://tanstack.com/query/latest). -It accepts the following props: +Then, replace the `TODO` in the component with the following: -- columns: (\`object\[]\`) The table's columns. +```tsx title="src/admin/routes/custom/page.tsx" +const { data, isLoading } = useQuery({ + queryFn: () => sdk.admin.product.list({ + limit, + offset, + q: search, + status: statusFilters, + order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined, + }), + queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]], +}) - - key: (\`string\`) The column's key in the passed \`data\` +// TODO configure data table +``` - - label: (\`string\`) The column's label shown in the table. If not provided, the \`key\` is used. +You use the `useQuery` hook to fetch the products from the Medusa application. In the `queryFn`, you call the `sdk.admin.product.list` method to fetch the products. You pass the following query parameters to the method: - - render: (\`(value: unknown) => React.ReactNode\`) By default, the data is shown as-is in the table. You can use this function to change how the value is rendered. The function receives the value is a parameter and returns a React node. -- data: (\`Record\\[]\`) The data to show in the table for the current page. The keys of each object should be in the \`columns\` array. -- pageSize: (\`number\`) The number of items to show per page. -- count: (\`number\`) The total number of items. -- currentPage: (\`number\`) A zero-based index indicating the current page's number. -- setCurrentPage: (\`(value: number) => void\`) A function used to change the current page. +- `limit`: The number of products to fetch per page. +- `offset`: The number of products to skip based on the current page. +- `q`: The search query, if set. +- `status`: The status filters, if set. +- `order`: The sorting order, if set. -*** +So, whenever the user changes the current page, search query, status filters, or sorting, the products are fetched based on the new parameters. -## Example +Next, you'll configure the data table. Medusa UI provides a `useDataTable` hook that helps you configure the data table. Add the following imports at the top of the file: -Use the `Table` component in any widget or UI route. +```tsx title="src/admin/routes/custom/page.tsx" +import { + // ... + useDataTable, +} from "@medusajs/ui" +``` -For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: +Then, replace the `TODO` in the component with the following: -```tsx title="src/admin/widgets/product-widget.tsx" -import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { StatusBadge } from "@medusajs/ui" -import { Table } from "../components/table" -import { useState } from "react" -import { Container } from "../components/container" +```tsx title="src/admin/routes/custom/page.tsx" +const table = useDataTable({ + columns, + data: data?.products || [], + getRowId: (row) => row.id, + rowCount: data?.count || 0, + isLoading, + pagination: { + state: pagination, + onPaginationChange: setPagination, + }, + search: { + state: search, + onSearchChange: setSearch, + }, + filtering: { + state: filtering, + onFilteringChange: setFiltering, + }, + filters, + sorting: { + // Pass the pagination state and updater to the table instance + state: sorting, + onSortingChange: setSorting, + }, +}) -const ProductWidget = () => { - const [currentPage, setCurrentPage] = useState(0) +// TODO render component +``` - return ( - - { - const isEnabled = value as boolean +The `useDataTable` hook accepts an object with the following properties: - return ( - - {isEnabled ? "Enabled" : "Disabled"} - - ) - }, - }, - ]} - data={[ - { - name: "John", - is_enabled: true, - }, - { - name: "Jane", - is_enabled: false, - }, - ]} - pageSize={2} - count={2} - currentPage={currentPage} - setCurrentPage={setCurrentPage} - /> - - ) -} +- `columns`: The columns to display in the data table. You created this using the `createDataTableColumnHelper` utility. +- `data`: The products fetched from the Medusa application. +- `getRowId`: A function that returns the unique ID of a row. +- `rowCount`: The total number of products that can be retrieved. This is used to determine the number of pages. +- `isLoading`: A boolean that indicates if the data is being fetched. +- `pagination`: An object to configure pagination. It accepts with the following properties: + - `state`: The pagination React state variable. + - `onPaginationChange`: A function that updates the pagination state. +- `search`: An object to configure searching. It accepts the following properties: + - `state`: The search query React state variable. + - `onSearchChange`: A function that updates the search query state. +- `filtering`: An object to configure filtering. It accepts the following properties: + - `state`: The filtering React state variable. + - `onFilteringChange`: A function that updates the filtering state. +- `filters`: The filters to display in the data table. You created this using the `createDataTableFilterHelper` utility. +- `sorting`: An object to configure sorting. It accepts the following properties: + - `state`: The sorting React state variable. + - `onSortingChange`: A function that updates the sorting state. -export const config = defineWidgetConfig({ - zone: "product.details.before", -}) +Finally, you'll render the data table. But first, add the following imports at the top of the page: -export default ProductWidget +```tsx title="src/admin/routes/custom/page.tsx" +import { + // ... + DataTable, +} from "@medusajs/ui" +import { SingleColumnLayout } from "../../layouts/single-column" +import { Container } from "../../components/container" ``` -This widget also uses the [Container](../container.mdx) custom component. +Aside from the `DataTable` component, you also import the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md) and [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) components implemented in other Admin Component guides. These components ensure a style consistent to other pages in the admin dashboard. -*** +Then, replace the `TODO` in the component with the following: -## Example With Data Fetching +```tsx title="src/admin/routes/custom/page.tsx" +return ( + + + + + Products +
+ + + +
+
+ + +
+
+
+) +``` -This section shows you how to use the `Table` component when fetching data from the Medusa application's API routes. +You render the `DataTable` component and pass the `table` instance as a prop. In the `DataTable` component, you render a toolbar showing a heading, filter menu, sorting menu, and a search input. You also show pagination after the table. -Assuming you've set up the JS SDK as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), create the UI route `src/admin/routes/custom/page.tsx` with the following content: +Lastly, export the component and the UI widget's configuration at the end of the file: -```tsx title="src/admin/routes/custom/page.tsx" collapsibleLines="1-10" expandButtonLabel="Show Imports" highlights={tableExampleHighlights} +```tsx title="src/admin/routes/custom/page.tsx" +// other imports... +import { defineRouteConfig } from "@medusajs/admin-sdk" +import { ChatBubbleLeftRight } from "@medusajs/icons" + +// ... + +export const config = defineRouteConfig({ + label: "Custom", + icon: ChatBubbleLeftRight, +}) + +export default CustomPage +``` + +If you start your Medusa application and go to `localhost:9000/app/custom`, you'll see the data table showing the list of products with pagination, filtering, searching, and sorting functionalities. + +### Full Example Code + +```tsx title="src/admin/routes/custom/page.tsx" import { defineRouteConfig } from "@medusajs/admin-sdk" import { ChatBubbleLeftRight } from "@medusajs/icons" +import { + Badge, + createDataTableColumnHelper, + createDataTableFilterHelper, + DataTable, + DataTableFilteringState, + DataTablePaginationState, + DataTableSortingState, + Heading, + useDataTable, +} from "@medusajs/ui" import { useQuery } from "@tanstack/react-query" import { SingleColumnLayout } from "../../layouts/single-column" -import { Table } from "../../components/table" import { sdk } from "../../lib/config" import { useMemo, useState } from "react" import { Container } from "../../components/container" -import { Header } from "../../components/header" +import { HttpTypes, ProductStatus } from "@medusajs/framework/types" + +const columnHelper = createDataTableColumnHelper() + +const columns = [ + columnHelper.accessor("title", { + header: "Title", + // Enables sorting for the column. + enableSorting: true, + // If omitted, the header will be used instead if it's a string, + // otherwise the accessor key (id) will be used. + sortLabel: "Title", + // If omitted the default value will be "A-Z" + sortAscLabel: "A-Z", + // If omitted the default value will be "Z-A" + sortDescLabel: "Z-A", + }), + columnHelper.accessor("status", { + header: "Status", + cell: ({ getValue }) => { + const status = getValue() + return ( + + {status === "published" ? "Published" : "Draft"} + + ) + }, + }), +] + +const filterHelper = createDataTableFilterHelper() + +const filters = [ + filterHelper.accessor("status", { + type: "select", + label: "Status", + options: [ + { + label: "Published", + value: "published", + }, + { + label: "Draft", + value: "draft", + }, + ], + }), +] + +const limit = 15 const CustomPage = () => { - const [currentPage, setCurrentPage] = useState(0) - const limit = 15 + const [pagination, setPagination] = useState({ + pageSize: limit, + pageIndex: 0, + }) + const [search, setSearch] = useState("") + const [filtering, setFiltering] = useState({}) + const [sorting, setSorting] = useState(null) + const offset = useMemo(() => { - return currentPage * limit - }, [currentPage]) + return pagination.pageIndex * limit + }, [pagination]) + const statusFilters = useMemo(() => { + return (filtering.status || []) as ProductStatus + }, [filtering]) - const { data } = useQuery({ + const { data, isLoading } = useQuery({ queryFn: () => sdk.admin.product.list({ limit, offset, + q: search, + status: statusFilters, + order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined, }), - queryKey: [["products", limit, offset]], + queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]], }) - // TODO display table + const table = useDataTable({ + columns, + data: data?.products || [], + getRowId: (row) => row.id, + rowCount: data?.count || 0, + isLoading, + pagination: { + state: pagination, + onPaginationChange: setPagination, + }, + search: { + state: search, + onSearchChange: setSearch, + }, + filtering: { + state: filtering, + onFilteringChange: setFiltering, + }, + filters, + sorting: { + // Pass the pagination state and updater to the table instance + state: sorting, + onSortingChange: setSorting, + }, + }) + + return ( + + + + + Products +
+ + + +
+
+ + +
+
+
+ ) } export const config = defineRouteConfig({ @@ -29326,62 +29142,6 @@ export const config = defineRouteConfig({ export default CustomPage ``` -In the `CustomPage` component, you define: - -- A state variable `currentPage` that stores the current page of the table. -- A `limit` variable, indicating how many items to retrieve per page -- An `offset` memoized variable indicating how many items to skip before the retrieved items. It's calculated as a multiplication of `currentPage` and `limit`. - -Then, you use `useQuery` from [Tanstack Query](https://tanstack.com/query/latest) to retrieve products using the JS SDK. You pass `limit` and `offset` as query parameters, and you set the `queryKey`, which is used for caching and revalidation, to be based on the key `products`, along with the current limit and offset. So, whenever the `offset` variable changes, the request is sent again to retrieve the products of the current page. - -You can change the query to send a request to a custom API route as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk#send-requests-to-custom-routes/index.html.md). - -Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency. - -`useQuery` returns an object containing `data`, which holds the response fields including the products and pagination fields. - -Then, to display the table, replace the `TODO` with the following: - -```tsx -return ( - - -
- {data && ( -
- )} - - -) -``` - -Aside from the `Table` component, this UI route also uses the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md), [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md), and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component. - -If `data` isn't `undefined`, you display the `Table` component passing it the following props: - -- `columns`: The columns to show. You only show the product's ID and title. -- `data`: The rows of the table. You pass it the `products` property of `data`. -- `pageSize`: The maximum number of items per page. You pass it the `count` property of `data`. -- `currentPage` and `setCurrentPage`: The current page and the function to change it. - -To test it out, log into the Medusa Admin and open `http://localhost:9000/app/custom`. You'll find a table of products with pagination. - # Forms - Admin Components @@ -29892,33 +29652,175 @@ export const EditForm = () => { - - - - - - - - + + + + + + + + + ) +} +``` + +You render a drawer, with a trigger button to open it. + +In the `Drawer.Content` component, you wrap the content with the `FormProvider` component from `react-hook-form`, passing it the details of the form you initialized earlier as props. + +In the `FormProvider`, you add a `form` component passing it the `handleSubmit` function you created earlier as the handler of the `onSubmit` event. + +You render the form's components inside the `Drawer.Body`. To render inputs, you use the `Controller` component imported from `react-hook-form`. + +Finally, in the `Drawer.Footer` component, you add buttons to save or cancel the form submission. + +### Use Edit Form Component + +You can use the `EditForm` component in your widget or UI route. + +For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: + +```tsx title="src/admin/widgets/product-widget.tsx" +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Container } from "../components/container" +import { Header } from "../components/header" +import { EditForm } from "../components/edit-form" + +const ProductWidget = () => { + return ( + +
, + }, + ]} + /> + + ) +} + +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) + +export default ProductWidget +``` + +This component uses the [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom components. + +It will add at the top of a product's details page a new section, and in its header you'll find an "Edit Item" button. If you click on it, it will open the drawer with your form. + + +# Header - Admin Components + +Each section in the Medusa Admin has a header with a title, and optionally a subtitle with buttons to perform an action. + +![Example of a header in a section](https://res.cloudinary.com/dza7lstvk/image/upload/v1728288562/Medusa%20Resources/header_dtz4gl.png) + +To create a component that uses the same header styling and structure, create the file `src/admin/components/header.tsx` with the following content: + +```tsx title="src/admin/components/header.tsx" +import { Heading, Button, Text } from "@medusajs/ui" +import React from "react" +import { Link, LinkProps } from "react-router-dom" +import { ActionMenu, ActionMenuProps } from "./action-menu" + +export type HeadingProps = { + title: string + subtitle?: string + actions?: ( + { + type: "button", + props: React.ComponentProps + link?: LinkProps + } | + { + type: "action-menu" + props: ActionMenuProps + } | + { + type: "custom" + children: React.ReactNode + } + )[] +} + +export const Header = ({ + title, + subtitle, + actions = [], +}: HeadingProps) => { + return ( +
+
+ {title} + {subtitle && ( + + {subtitle} + + )} +
+ {actions.length > 0 && ( +
+ {actions.map((action, index) => ( + <> + {action.type === "button" && ( + + )} + {action.type === "action-menu" && ( + + )} + {action.type === "custom" && action.children} + + ))} +
+ )} +
) } ``` -You render a drawer, with a trigger button to open it. +The `Header` component shows a title, and optionally a subtitle and action buttons. -In the `Drawer.Content` component, you wrap the content with the `FormProvider` component from `react-hook-form`, passing it the details of the form you initialized earlier as props. +The component also uses the [Action Menu](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/action-menu/index.html.md) custom component. -In the `FormProvider`, you add a `form` component passing it the `handleSubmit` function you created earlier as the handler of the `onSubmit` event. +It accepts the following props: -You render the form's components inside the `Drawer.Body`. To render inputs, you use the `Controller` component imported from `react-hook-form`. +- title: (\`string\`) The section's title. +- subtitle: (\`string\`) The section's subtitle. +- actions: (\`object\[]\`) An array of actions to show. -Finally, in the `Drawer.Footer` component, you add buttons to save or cancel the form submission. + - type: (\`button\` \\| \`action-menu\` \\| \`custom\`) The type of action to add. -### Use Edit Form Component + \- If its value is \`button\`, it'll show a button that can have a link or an on-click action. -You can use the `EditForm` component in your widget or UI route. + \- If its value is \`action-menu\`, it'll show a three dot icon with a dropdown of actions. + + \- If its value is \`custom\`, you can pass any React nodes to render. + + - props: (object) + + - children: (React.ReactNode) This property is only accepted if \`type\` is \`custom\`. Its content is rendered as part of the actions. + +*** + +## Example + +Use the `Header` component in any widget or UI route. For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: @@ -29926,17 +29828,23 @@ For example, create the widget `src/admin/widgets/product-widget.tsx` with the f import { defineWidgetConfig } from "@medusajs/admin-sdk" import { Container } from "../components/container" import { Header } from "../components/header" -import { EditForm } from "../components/edit-form" const ProductWidget = () => { return ( -
, + type: "button", + props: { + children: "Click me", + variant: "secondary", + onClick: () => { + alert("You clicked the button.") + }, + }, }, ]} /> @@ -29951,553 +29859,678 @@ export const config = defineWidgetConfig({ export default ProductWidget ``` -This component uses the [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom components. +This widget also uses a [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) custom component. -It will add at the top of a product's details page a new section, and in its header you'll find an "Edit Item" button. If you click on it, it will open the drawer with your form. +# JSON View - Admin Components -# Two Column Layout - Admin Components +Detail pages in the Medusa Admin show a JSON section to view the current page's details in JSON format. -The Medusa Admin has pages with two columns of content. +![Example of a JSON section in the admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728295129/Medusa%20Resources/json_dtbsgm.png) -This doesn't include the sidebar, only the main content. +To create a component that shows a JSON section in your customizations, create the file `src/admin/components/json-view-section.tsx` with the following content: -![An example of an admin page with two columns](https://res.cloudinary.com/dza7lstvk/image/upload/v1728286690/Medusa%20Resources/two-column_sdnkg0.png) +```tsx title="src/admin/components/json-view-section.tsx" +import { + ArrowUpRightOnBox, + Check, + SquareTwoStack, + TriangleDownMini, + XMarkMini, +} from "@medusajs/icons" +import { + Badge, + Container, + Drawer, + Heading, + IconButton, + Kbd, +} from "@medusajs/ui" +import Primitive from "@uiw/react-json-view" +import { CSSProperties, MouseEvent, Suspense, useState } from "react" -To create a layout that you can use in UI routes to support two columns of content, create the component `src/admin/layouts/two-column.tsx` with the following content: +type JsonViewSectionProps = { + data: object + title?: string +} -```tsx title="src/admin/layouts/two-column.tsx" -export type TwoColumnLayoutProps = { - firstCol: React.ReactNode - secondCol: React.ReactNode +export const JsonViewSection = ({ data }: JsonViewSectionProps) => { + const numberOfKeys = Object.keys(data).length + + return ( + +
+ JSON + + {numberOfKeys} keys + +
+ + + + + + + +
+
+ + + + {numberOfKeys} + + + +
+
+ + esc + + + + + + +
+
+ +
+
} + > + + } /> + ( + null + )} + /> + ( + undefined + )} + /> + { + return ( + + {Object.keys(value as object).length} items + + ) + }} + /> + + + + + : + + { + return + }} + /> + + + +
+
+
+
+ ) +} + +type CopiedProps = { + style?: CSSProperties + value: object | undefined } -export const TwoColumnLayout = ({ - firstCol, - secondCol, -}: TwoColumnLayoutProps) => { +const Copied = ({ style, value }: CopiedProps) => { + const [copied, setCopied] = useState(false) + + const handler = (e: MouseEvent) => { + e.stopPropagation() + setCopied(true) + + if (typeof value === "string") { + navigator.clipboard.writeText(value) + } else { + const json = JSON.stringify(value, null, 2) + navigator.clipboard.writeText(json) + } + + setTimeout(() => { + setCopied(false) + }, 2000) + } + + const styl = { whiteSpace: "nowrap", width: "20px" } + + if (copied) { + return ( + + + + ) + } + return ( -
-
- {firstCol} -
-
- {secondCol} -
-
+ + + ) } ``` -The `TwoColumnLayout` accepts two props: +The `JsonViewSection` component shows a section with the "JSON" title and a button to show the data as JSON in a drawer or side window. -- `firstCol` indicating the content of the first column. -- `secondCol` indicating the content of the second column. +The `JsonViewSection` accepts a `data` prop, which is the data to show as a JSON object in the drawer. *** ## Example -Use the `TwoColumnLayout` component in your UI routes that have a single column. For example: +Use the `JsonViewSection` component in any widget or UI route. -```tsx title="src/admin/routes/custom/page.tsx" highlights={[["9"]]} -import { defineRouteConfig } from "@medusajs/admin-sdk" -import { ChatBubbleLeftRight } from "@medusajs/icons" -import { Container } from "../../components/container" -import { Header } from "../../components/header" -import { TwoColumnLayout } from "../../layouts/two-column" +For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: -const CustomPage = () => { - return ( - -
- - } - secondCol={ - -
- - } - /> - ) +```tsx title="src/admin/widgets/product-widget.tsx" +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { JsonViewSection } from "../components/json-view-section" + +const ProductWidget = () => { + return } -export const config = defineRouteConfig({ - label: "Custom", - icon: ChatBubbleLeftRight, +export const config = defineWidgetConfig({ + zone: "product.details.before", }) -export default CustomPage +export default ProductWidget ``` -This UI route also uses [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header]() custom components. +This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`. -# Data Table - Admin Components +# Container - Admin Components -This component is available after [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0). +The Medusa Admin wraps each section of a page in a container. -The [DataTable component in Medusa UI](https://docs.medusajs.com/ui/components/data-table/index.html.md) allows you to display data in a table with sorting, filtering, and pagination. +![Example of a container in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728287102/Medusa%20Resources/container_soenir.png) -You can use this component in your Admin Extensions to display data in a table format, especially if they're retrieved from API routes of the Medusa application. +To create a component that uses the same container styling in your widgets or UI routes, create the file `src/admin/components/container.tsx` with the following content: -Refer to the [Medusa UI documentation](https://docs.medusajs.com/ui/components/data-table/index.html.md) for detailed information about the DataTable component and its different usages. +```tsx +import { + Container as UiContainer, + clx, +} from "@medusajs/ui" -## Example: DataTable with Data Fetching +type ContainerProps = React.ComponentProps -In this example, you'll create a UI widget that shows the list of products retrieved from the [List Products API Route](https://docs.medusajs.com/api/admin#products_getproducts) in a data table with pagination, filtering, searching, and sorting. +export const Container = (props: ContainerProps) => { + return ( + + ) +} +``` -Start by initializing the columns in the data table. To do that, use the `createDataTableColumnHelper` from Medusa UI: +The `Container` component re-uses the component from the [Medusa UI package](https://docs.medusajs.com/ui/components/container/index.html.md) and applies to it classes to match the Medusa Admin's design conventions. -```tsx title="src/admin/routes/custom/page.tsx" -import { - createDataTableColumnHelper, -} from "@medusajs/ui" -import { - HttpTypes, -} from "@medusajs/framework/types" +*** -const columnHelper = createDataTableColumnHelper() +## Example -const columns = [ - columnHelper.accessor("title", { - header: "Title", - // Enables sorting for the column. - enableSorting: true, - // If omitted, the header will be used instead if it's a string, - // otherwise the accessor key (id) will be used. - sortLabel: "Title", - // If omitted the default value will be "A-Z" - sortAscLabel: "A-Z", - // If omitted the default value will be "Z-A" - sortDescLabel: "Z-A", - }), - columnHelper.accessor("status", { - header: "Status", - cell: ({ getValue }) => { - const status = getValue() - return ( - - {status === "published" ? "Published" : "Draft"} - - ) - }, - }), -] -``` +Use that `Container` component in any widget or UI route. -`createDataTableColumnHelper` utility creates a column helper that helps you define the columns for the data table. The column helper has an `accessor` method that accepts two parameters: +For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: -1. The column's key in the table's data. -2. An object with the following properties: - - `header`: The column's header. - - `cell`: (optional) By default, a data's value for a column is displayed as a string. Use this property to specify custom rendering of the value. It accepts a function that returns a string or a React node. The function receives an object that has a `getValue` property function to retrieve the raw value of the cell. - - `enableSorting`: (optional) A boolean that enables sorting data by this column. - - `sortLabel`: (optional) The label for the sorting button. If omitted, the `header` will be used instead if it's a string, otherwise the accessor key (id) will be used. - - `sortAscLabel`: (optional) The label for the ascending sorting button. If omitted, the default value will be "A-Z". - - `sortDescLabel`: (optional) The label for the descending sorting button. If omitted, the default value will be "Z-A". +```tsx title="src/admin/widgets/product-widget.tsx" +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Container } from "../components/container" +import { Header } from "../components/header" -Next, you'll define the filters that can be applied to the data table. You'll configure filtering by product status. +const ProductWidget = () => { + return ( + +
+ + ) +} -To define the filters, add the following: +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) -```tsx title="src/admin/routes/custom/page.tsx" -// other imports... -import { - // ... - createDataTableFilterHelper, -} from "@medusajs/ui" +export default ProductWidget +``` -const filterHelper = createDataTableFilterHelper() +This widget also uses a [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component. -const filters = [ - filterHelper.accessor("status", { - type: "select", - label: "Status", - options: [ - { - label: "Published", - value: "published", - }, - { - label: "Draft", - value: "draft", - }, - ], - }), -] -``` -`createDataTableFilterHelper` utility creates a filter helper that helps you define the filters for the data table. The filter helper has an `accessor` method that accepts two parameters: +# Section Row - Admin Components -1. The key of a column in the table's data. -2. An object with the following properties: - - `type`: The type of filter. It can be either: - - `select`: A select dropdown allowing users to choose multiple values. - - `radio`: A radio button allowing users to choose one value. - - `date`: A date picker allowing users to choose a date. - - `label`: The filter's label. - - `options`: An array of objects with `label` and `value` properties. The `label` is the option's label, and the `value` is the value to filter by. +The Medusa Admin often shows information in rows of label-values, such as when showing a product's details. -You'll now start creating the UI widget's component. Start by adding the necessary state variables: +![Example of a section row in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728292781/Medusa%20Resources/section-row_kknbnw.png) -```tsx title="src/admin/routes/custom/page.tsx" -// other imports... -import { - // ... - DataTablePaginationState, - DataTableFilteringState, - DataTableSortingState, -} from "@medusajs/ui" -import { useMemo, useState } from "react" +To create a component that shows information in the same structure, create the file `src/admin/components/section-row.tsx` with the following content: -// ... +```tsx title="src/admin/components/section-row.tsx" +import { Text, clx } from "@medusajs/ui" -const limit = 15 +export type SectionRowProps = { + title: string + value?: React.ReactNode | string | null + actions?: React.ReactNode +} -const CustomPage = () => { - const [pagination, setPagination] = useState({ - pageSize: limit, - pageIndex: 0, - }) - const [search, setSearch] = useState("") - const [filtering, setFiltering] = useState({}) - const [sorting, setSorting] = useState(null) +export const SectionRow = ({ title, value, actions }: SectionRowProps) => { + const isValueString = typeof value === "string" || !value - const offset = useMemo(() => { - return pagination.pageIndex * limit - }, [pagination]) - const statusFilters = useMemo(() => { - return (filtering.status || []) as ProductStatus - }, [filtering]) + return ( +
+ + {title} + + + {isValueString ? ( + + {value ?? "-"} + + ) : ( +
{value}
+ )} - // TODO add data fetching logic + {actions &&
{actions}
} +
+ ) } ``` -In the component, you've added the following state variables: +The `SectionRow` component shows a title and a value in the same row. -- `pagination`: An object of type `DataTablePaginationState` that holds the pagination state. It has two properties: - - `pageSize`: The number of items to show per page. - - `pageIndex`: The current page index. -- `search`: A string that holds the search query. -- `filtering`: An object of type `DataTableFilteringState` that holds the filtering state. -- `sorting`: An object of type `DataTableSortingState` that holds the sorting state. +It accepts the following props: -You've also added two memoized variables: +- title: (\`string\`) The title to show on the left side. +- value: (\`React.ReactNode\` \\| \`string\` \\| \`null\`) The value to show on the right side. +- actions: (\`React.ReactNode\`) The actions to show at the end of the row. -- `offset`: How many items to skip when fetching data based on the current page. -- `statusFilters`: The selected status filters, if any. +*** -Next, you'll fetch the products from the Medusa application. Assuming you have the JS SDK configured as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), add the following imports at the top of the file: +## Example -```tsx title="src/admin/routes/custom/page.tsx" -import { sdk } from "../../lib/config" -import { useQuery } from "@tanstack/react-query" -``` +Use the `SectionRow` component in any widget or UI route. -This imports the JS SDK instance and `useQuery` from [Tanstack Query](https://tanstack.com/query/latest). +For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: -Then, replace the `TODO` in the component with the following: +```tsx title="src/admin/widgets/product-widget.tsx" +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Container } from "../components/container" +import { Header } from "../components/header" +import { SectionRow } from "../components/section-row" -```tsx title="src/admin/routes/custom/page.tsx" -const { data, isLoading } = useQuery({ - queryFn: () => sdk.admin.product.list({ - limit, - offset, - q: search, - status: statusFilters, - order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined, - }), - queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]], +const ProductWidget = () => { + return ( + +
+ + + ) +} + +export const config = defineWidgetConfig({ + zone: "product.details.before", }) -// TODO configure data table +export default ProductWidget ``` -You use the `useQuery` hook to fetch the products from the Medusa application. In the `queryFn`, you call the `sdk.admin.product.list` method to fetch the products. You pass the following query parameters to the method: +This widget also uses the [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component. -- `limit`: The number of products to fetch per page. -- `offset`: The number of products to skip based on the current page. -- `q`: The search query, if set. -- `status`: The status filters, if set. -- `order`: The sorting order, if set. -So, whenever the user changes the current page, search query, status filters, or sorting, the products are fetched based on the new parameters. +# Table - Admin Components -Next, you'll configure the data table. Medusa UI provides a `useDataTable` hook that helps you configure the data table. Add the following imports at the top of the file: +If you're using [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0), it's recommended to use the [Data Table](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/data-table/index.html.md) component instead. -```tsx title="src/admin/routes/custom/page.tsx" -import { - // ... - useDataTable, -} from "@medusajs/ui" -``` +The listing pages in the Admin show a table with pagination. -Then, replace the `TODO` in the component with the following: +![Example of a table in the product listing page](https://res.cloudinary.com/dza7lstvk/image/upload/v1728295658/Medusa%20Resources/list_ddt9zc.png) -```tsx title="src/admin/routes/custom/page.tsx" -const table = useDataTable({ - columns, - data: data?.products || [], - getRowId: (row) => row.id, - rowCount: data?.count || 0, - isLoading, - pagination: { - state: pagination, - onPaginationChange: setPagination, - }, - search: { - state: search, - onSearchChange: setSearch, - }, - filtering: { - state: filtering, - onFilteringChange: setFiltering, - }, - filters, - sorting: { - // Pass the pagination state and updater to the table instance - state: sorting, - onSortingChange: setSorting, - }, -}) +To create a component that shows a table with pagination, create the file `src/admin/components/table.tsx` with the following content: -// TODO render component -``` +```tsx title="src/admin/components/table.tsx" +import { useMemo } from "react" +import { Table as UiTable } from "@medusajs/ui" -The `useDataTable` hook accepts an object with the following properties: +export type TableProps = { + columns: { + key: string + label?: string + render?: (value: unknown) => React.ReactNode + }[] + data: Record[] + pageSize: number + count: number + currentPage: number + setCurrentPage: (value: number) => void +} -- `columns`: The columns to display in the data table. You created this using the `createDataTableColumnHelper` utility. -- `data`: The products fetched from the Medusa application. -- `getRowId`: A function that returns the unique ID of a row. -- `rowCount`: The total number of products that can be retrieved. This is used to determine the number of pages. -- `isLoading`: A boolean that indicates if the data is being fetched. -- `pagination`: An object to configure pagination. It accepts with the following properties: - - `state`: The pagination React state variable. - - `onPaginationChange`: A function that updates the pagination state. -- `search`: An object to configure searching. It accepts the following properties: - - `state`: The search query React state variable. - - `onSearchChange`: A function that updates the search query state. -- `filtering`: An object to configure filtering. It accepts the following properties: - - `state`: The filtering React state variable. - - `onFilteringChange`: A function that updates the filtering state. -- `filters`: The filters to display in the data table. You created this using the `createDataTableFilterHelper` utility. -- `sorting`: An object to configure sorting. It accepts the following properties: - - `state`: The sorting React state variable. - - `onSortingChange`: A function that updates the sorting state. +export const Table = ({ + columns, + data, + pageSize, + count, + currentPage, + setCurrentPage, +}: TableProps) => { + const pageCount = useMemo(() => { + return Math.ceil(count / pageSize) + }, [data, pageSize]) -Finally, you'll render the data table. But first, add the following imports at the top of the page: + const canNextPage = useMemo(() => { + return currentPage < pageCount - 1 + }, [currentPage, pageCount]) + const canPreviousPage = useMemo(() => { + return currentPage - 1 >= 0 + }, [currentPage]) -```tsx title="src/admin/routes/custom/page.tsx" -import { - // ... - DataTable, -} from "@medusajs/ui" -import { SingleColumnLayout } from "../../layouts/single-column" -import { Container } from "../../components/container" + const nextPage = () => { + if (canNextPage) { + setCurrentPage(currentPage + 1) + } + } + + const previousPage = () => { + if (canPreviousPage) { + setCurrentPage(currentPage - 1) + } + } + + return ( +
+ + + + {columns.map((column, index) => ( + + {column.label || column.key} + + ))} + + + + {data.map((item, index) => { + const rowIndex = "id" in item ? item.id as string : index + return ( + + {columns.map((column, index) => ( + + <> + {column.render && column.render(item[column.key])} + {!column.render && ( + <>{item[column.key] as string} + )} + + + ))} + + ) + })} + + + +
+ ) +} ``` -Aside from the `DataTable` component, you also import the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md) and [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md) components implemented in other Admin Component guides. These components ensure a style consistent to other pages in the admin dashboard. +The `Table` component uses the component from the [UI package](https://docs.medusajs.com/ui/components/table/index.html.md), with additional styling and rendering of data. -Then, replace the `TODO` in the component with the following: +It accepts the following props: -```tsx title="src/admin/routes/custom/page.tsx" -return ( - - - - - Products -
- - - -
-
- - -
-
-
-) -``` +- columns: (\`object\[]\`) The table's columns. + + - key: (\`string\`) The column's key in the passed \`data\` + + - label: (\`string\`) The column's label shown in the table. If not provided, the \`key\` is used. + + - render: (\`(value: unknown) => React.ReactNode\`) By default, the data is shown as-is in the table. You can use this function to change how the value is rendered. The function receives the value is a parameter and returns a React node. +- data: (\`Record\\[]\`) The data to show in the table for the current page. The keys of each object should be in the \`columns\` array. +- pageSize: (\`number\`) The number of items to show per page. +- count: (\`number\`) The total number of items. +- currentPage: (\`number\`) A zero-based index indicating the current page's number. +- setCurrentPage: (\`(value: number) => void\`) A function used to change the current page. -You render the `DataTable` component and pass the `table` instance as a prop. In the `DataTable` component, you render a toolbar showing a heading, filter menu, sorting menu, and a search input. You also show pagination after the table. +*** -Lastly, export the component and the UI widget's configuration at the end of the file: +## Example -```tsx title="src/admin/routes/custom/page.tsx" -// other imports... -import { defineRouteConfig } from "@medusajs/admin-sdk" -import { ChatBubbleLeftRight } from "@medusajs/icons" +Use the `Table` component in any widget or UI route. -// ... +For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: -export const config = defineRouteConfig({ - label: "Custom", - icon: ChatBubbleLeftRight, +```tsx title="src/admin/widgets/product-widget.tsx" +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { StatusBadge } from "@medusajs/ui" +import { Table } from "../components/table" +import { useState } from "react" +import { Container } from "../components/container" + +const ProductWidget = () => { + const [currentPage, setCurrentPage] = useState(0) + + return ( + +
{ + const isEnabled = value as boolean + + return ( + + {isEnabled ? "Enabled" : "Disabled"} + + ) + }, + }, + ]} + data={[ + { + name: "John", + is_enabled: true, + }, + { + name: "Jane", + is_enabled: false, + }, + ]} + pageSize={2} + count={2} + currentPage={currentPage} + setCurrentPage={setCurrentPage} + /> + + ) +} + +export const config = defineWidgetConfig({ + zone: "product.details.before", }) -export default CustomPage +export default ProductWidget ``` -If you start your Medusa application and go to `localhost:9000/app/custom`, you'll see the data table showing the list of products with pagination, filtering, searching, and sorting functionalities. +This widget also uses the [Container](../container.mdx) custom component. -### Full Example Code +*** -```tsx title="src/admin/routes/custom/page.tsx" +## Example With Data Fetching + +This section shows you how to use the `Table` component when fetching data from the Medusa application's API routes. + +Assuming you've set up the JS SDK as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md), create the UI route `src/admin/routes/custom/page.tsx` with the following content: + +```tsx title="src/admin/routes/custom/page.tsx" collapsibleLines="1-10" expandButtonLabel="Show Imports" highlights={tableExampleHighlights} import { defineRouteConfig } from "@medusajs/admin-sdk" import { ChatBubbleLeftRight } from "@medusajs/icons" -import { - Badge, - createDataTableColumnHelper, - createDataTableFilterHelper, - DataTable, - DataTableFilteringState, - DataTablePaginationState, - DataTableSortingState, - Heading, - useDataTable, -} from "@medusajs/ui" import { useQuery } from "@tanstack/react-query" import { SingleColumnLayout } from "../../layouts/single-column" +import { Table } from "../../components/table" import { sdk } from "../../lib/config" import { useMemo, useState } from "react" import { Container } from "../../components/container" -import { HttpTypes, ProductStatus } from "@medusajs/framework/types" - -const columnHelper = createDataTableColumnHelper() - -const columns = [ - columnHelper.accessor("title", { - header: "Title", - // Enables sorting for the column. - enableSorting: true, - // If omitted, the header will be used instead if it's a string, - // otherwise the accessor key (id) will be used. - sortLabel: "Title", - // If omitted the default value will be "A-Z" - sortAscLabel: "A-Z", - // If omitted the default value will be "Z-A" - sortDescLabel: "Z-A", - }), - columnHelper.accessor("status", { - header: "Status", - cell: ({ getValue }) => { - const status = getValue() - return ( - - {status === "published" ? "Published" : "Draft"} - - ) - }, - }), -] - -const filterHelper = createDataTableFilterHelper() - -const filters = [ - filterHelper.accessor("status", { - type: "select", - label: "Status", - options: [ - { - label: "Published", - value: "published", - }, - { - label: "Draft", - value: "draft", - }, - ], - }), -] - -const limit = 15 +import { Header } from "../../components/header" const CustomPage = () => { - const [pagination, setPagination] = useState({ - pageSize: limit, - pageIndex: 0, - }) - const [search, setSearch] = useState("") - const [filtering, setFiltering] = useState({}) - const [sorting, setSorting] = useState(null) - + const [currentPage, setCurrentPage] = useState(0) + const limit = 15 const offset = useMemo(() => { - return pagination.pageIndex * limit - }, [pagination]) - const statusFilters = useMemo(() => { - return (filtering.status || []) as ProductStatus - }, [filtering]) + return currentPage * limit + }, [currentPage]) - const { data, isLoading } = useQuery({ + const { data } = useQuery({ queryFn: () => sdk.admin.product.list({ limit, offset, - q: search, - status: statusFilters, - order: sorting ? `${sorting.desc ? "-" : ""}${sorting.id}` : undefined, }), - queryKey: [["products", limit, offset, search, statusFilters, sorting?.id, sorting?.desc]], - }) - - const table = useDataTable({ - columns, - data: data?.products || [], - getRowId: (row) => row.id, - rowCount: data?.count || 0, - isLoading, - pagination: { - state: pagination, - onPaginationChange: setPagination, - }, - search: { - state: search, - onSearchChange: setSearch, - }, - filtering: { - state: filtering, - onFilteringChange: setFiltering, - }, - filters, - sorting: { - // Pass the pagination state and updater to the table instance - state: sorting, - onSortingChange: setSorting, - }, + queryKey: [["products", limit, offset]], }) - return ( - - - - - Products -
- - - -
-
- - -
-
-
- ) + // TODO display table } -export const config = defineRouteConfig({ - label: "Custom", - icon: ChatBubbleLeftRight, -}) +export const config = defineRouteConfig({ + label: "Custom", + icon: ChatBubbleLeftRight, +}) + +export default CustomPage +``` + +In the `CustomPage` component, you define: + +- A state variable `currentPage` that stores the current page of the table. +- A `limit` variable, indicating how many items to retrieve per page +- An `offset` memoized variable indicating how many items to skip before the retrieved items. It's calculated as a multiplication of `currentPage` and `limit`. + +Then, you use `useQuery` from [Tanstack Query](https://tanstack.com/query/latest) to retrieve products using the JS SDK. You pass `limit` and `offset` as query parameters, and you set the `queryKey`, which is used for caching and revalidation, to be based on the key `products`, along with the current limit and offset. So, whenever the `offset` variable changes, the request is sent again to retrieve the products of the current page. + +You can change the query to send a request to a custom API route as explained in [this guide](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk#send-requests-to-custom-routes/index.html.md). + +Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency. + +`useQuery` returns an object containing `data`, which holds the response fields including the products and pagination fields. + +Then, to display the table, replace the `TODO` with the following: + +```tsx +return ( + + +
+ {data && ( +
+ )} + + +) +``` + +Aside from the `Table` component, this UI route also uses the [SingleColumnLayout](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/layouts/single-column/index.html.md), [Container](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/container/index.html.md), and [Header](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/admin-components/components/header/index.html.md) custom component. -export default CustomPage -``` +If `data` isn't `undefined`, you display the `Table` component passing it the following props: + +- `columns`: The columns to show. You only show the product's ID and title. +- `data`: The rows of the table. You pass it the `products` property of `data`. +- `pageSize`: The maximum number of items per page. You pass it the `count` property of `data`. +- `currentPage` and `setCurrentPage`: The current page and the function to change it. + +To test it out, log into the Medusa Admin and open `http://localhost:9000/app/custom`. You'll find a table of products with pagination. # Service Factory Reference @@ -30565,46 +30598,6 @@ const posts = await postModuleService.createPosts([ If an array is passed of the method, an array of the created records is also returned. -# delete Method - Service Factory Reference - -This method deletes one or more records. - -## Delete One Record - -```ts -await postModuleService.deletePosts("123") -``` - -To delete one record, pass its ID as a parameter of the method. - -*** - -## Delete Multiple Records - -```ts -await postModuleService.deletePosts([ - "123", - "321", -]) -``` - -To delete multiple records, pass an array of IDs as a parameter of the method. - -*** - -## Delete Records Matching Filters - -```ts -await postModuleService.deletePosts({ - name: "My Post", -}) -``` - -To delete records matching a set of filters, pass an object of filters as a parameter. - -Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md). - - # listAndCount Method - Service Factory Reference This method retrieves a list of records with the total count. @@ -30741,6 +30734,133 @@ The method returns an array with two items: 2. The second is the total count of records. +# delete Method - Service Factory Reference + +This method deletes one or more records. + +## Delete One Record + +```ts +await postModuleService.deletePosts("123") +``` + +To delete one record, pass its ID as a parameter of the method. + +*** + +## Delete Multiple Records + +```ts +await postModuleService.deletePosts([ + "123", + "321", +]) +``` + +To delete multiple records, pass an array of IDs as a parameter of the method. + +*** + +## Delete Records Matching Filters + +```ts +await postModuleService.deletePosts({ + name: "My Post", +}) +``` + +To delete records matching a set of filters, pass an object of filters as a parameter. + +Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md). + + +# restore Method - Service Factory Reference + +This method restores one or more records of the data model that were [soft-deleted](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/methods/soft-delete/index.html.md). + +## Restore One Record + +```ts +const restoredPosts = await postModuleService.restorePosts("123") +``` + +### Parameters + +To restore one record, pass its ID as a parameter of the method. + +### Returns + +The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs. + +For example, the returned object of the above example is: + +```ts +restoredPosts = { + post_id: ["123"], +} +``` + +*** + +## Restore Multiple Records + +```ts +const restoredPosts = await postModuleService.restorePosts([ + "123", + "321", +]) +``` + +### Parameters + +To restore multiple records, pass an array of IDs as a parameter of the method. + +### Returns + +The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs. + +For example, the returned object of the above example is: + +```ts +restoredPosts = { + post_id: [ + "123", + "321", + ], +} +``` + +*** + +## Restore Records Matching Filters + +```ts +const restoredPosts = await postModuleService.restorePosts({ + name: "My Post", +}) +``` + +### Parameters + +To restore records matching a set of filters, pass an object of fitlers as a parameter of the method. + +Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md). + +### Returns + +The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs. + +For example, the returned object of the above example is: + +```ts +restoredPosts = { + post_id: [ + "123", + ], +} +``` + + # list Method - Service Factory Reference This method retrieves a list of records. @@ -30840,80 +30960,167 @@ The method returns an array of records. The number of records is less than or eq ## Sort Records ```ts -const posts = await postModuleService.listPosts({}, { - order: { - name: "ASC", - }, +const posts = await postModuleService.listPosts({}, { + order: { + name: "ASC", + }, +}) +``` + +### Parameters + +To sort records by one or more properties, pass to the second object parameter the `order` property. Its value is an object whose keys are the property names, and values can either be: + +- `ASC` to sort by this property in the ascending order. +- `DESC` to sort by this property in the descending order. + +### Returns + +The method returns an array of the first `15` records matching the filters. + + +# retrieve Method - Service Factory Reference + +This method retrieves one record of the data model by its ID. + +## Retrieve a Record + +```ts +const post = await postModuleService.retrievePost("123") +``` + +### Parameters + +Pass the ID of the record to retrieve. + +### Returns + +The method returns the record as an object. + +*** + +## Retrieve a Record's Relations + +This applies to relations between data models of the same module. To retrieve linked records of different modules, use [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md). + +```ts +const post = await postModuleService.retrievePost("123", { + relations: ["author"], +}) +``` + +### Parameters + +To retrieve the data model with relations, pass as a second parameter of the method an object with the property `relations`. `relations`'s value is an array of relation names. + +### Returns + +The method returns the record as an object. + +*** + +## Select Properties to Retrieve + +```ts +const post = await postModuleService.retrievePost("123", { + select: ["id", "name"], }) ``` ### Parameters -To sort records by one or more properties, pass to the second object parameter the `order` property. Its value is an object whose keys are the property names, and values can either be: - -- `ASC` to sort by this property in the ascending order. -- `DESC` to sort by this property in the descending order. +By default, all of the record's properties are retrieved. To select specific ones, pass in the second object parameter a `select` property. Its value is an array of property names. ### Returns -The method returns an array of the first `15` records matching the filters. +The method returns the record as an object. -# retrieve Method - Service Factory Reference +# softDelete Method - Service Factory Reference -This method retrieves one record of the data model by its ID. +This method soft deletes one or more records of the data model. -## Retrieve a Record +## Soft Delete One Record ```ts -const post = await postModuleService.retrievePost("123") +const deletedPosts = await postModuleService.softDeletePosts( + "123" +) ``` ### Parameters -Pass the ID of the record to retrieve. +To soft delete a record, pass its ID as a parameter of the method. ### Returns -The method returns the record as an object. +The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs. -*** +For example, the returned object of the above example is: -## Retrieve a Record's Relations +```ts +deletedPosts = { + post_id: ["123"], +} +``` -This applies to relations between data models of the same module. To retrieve linked records of different modules, use [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md). +*** + +## Soft Delete Multiple Records ```ts -const post = await postModuleService.retrievePost("123", { - relations: ["author"], -}) +const deletedPosts = await postModuleService.softDeletePosts([ + "123", + "321", +]) ``` ### Parameters -To retrieve the data model with relations, pass as a second parameter of the method an object with the property `relations`. `relations`'s value is an array of relation names. +To soft delete multiple records, pass an array of IDs as a parameter of the method. ### Returns -The method returns the record as an object. +The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs. + +For example, the returned object of the above example is: + +```ts +deletedPosts = { + post_id: [ + "123", + "321", + ], +} +``` *** -## Select Properties to Retrieve +## Soft Delete Records Matching Filters ```ts -const post = await postModuleService.retrievePost("123", { - select: ["id", "name"], +const deletedPosts = await postModuleService.softDeletePosts({ + name: "My Post", }) ``` ### Parameters -By default, all of the record's properties are retrieved. To select specific ones, pass in the second object parameter a `select` property. Its value is an array of property names. +To soft delete records matching a set of filters, pass an object of filters as a parameter. + +Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md). ### Returns -The method returns the record as an object. +The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs. + +For example, the returned object of the above example is: + +```ts +deletedPosts = { + post_id: ["123"], +} +``` # update Method - Service Factory Reference @@ -31039,180 +31246,6 @@ Learn more about accepted filters in [this documentation](https://docs.medusajs. The method returns an array of objects of updated records. -# restore Method - Service Factory Reference - -This method restores one or more records of the data model that were [soft-deleted](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/methods/soft-delete/index.html.md). - -## Restore One Record - -```ts -const restoredPosts = await postModuleService.restorePosts("123") -``` - -### Parameters - -To restore one record, pass its ID as a parameter of the method. - -### Returns - -The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs. - -For example, the returned object of the above example is: - -```ts -restoredPosts = { - post_id: ["123"], -} -``` - -*** - -## Restore Multiple Records - -```ts -const restoredPosts = await postModuleService.restorePosts([ - "123", - "321", -]) -``` - -### Parameters - -To restore multiple records, pass an array of IDs as a parameter of the method. - -### Returns - -The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs. - -For example, the returned object of the above example is: - -```ts -restoredPosts = { - post_id: [ - "123", - "321", - ], -} -``` - -*** - -## Restore Records Matching Filters - -```ts -const restoredPosts = await postModuleService.restorePosts({ - name: "My Post", -}) -``` - -### Parameters - -To restore records matching a set of filters, pass an object of fitlers as a parameter of the method. - -Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md). - -### Returns - -The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of restored records' IDs. - -For example, the returned object of the above example is: - -```ts -restoredPosts = { - post_id: [ - "123", - ], -} -``` - - -# softDelete Method - Service Factory Reference - -This method soft deletes one or more records of the data model. - -## Soft Delete One Record - -```ts -const deletedPosts = await postModuleService.softDeletePosts( - "123" -) -``` - -### Parameters - -To soft delete a record, pass its ID as a parameter of the method. - -### Returns - -The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs. - -For example, the returned object of the above example is: - -```ts -deletedPosts = { - post_id: ["123"], -} -``` - -*** - -## Soft Delete Multiple Records - -```ts -const deletedPosts = await postModuleService.softDeletePosts([ - "123", - "321", -]) -``` - -### Parameters - -To soft delete multiple records, pass an array of IDs as a parameter of the method. - -### Returns - -The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs. - -For example, the returned object of the above example is: - -```ts -deletedPosts = { - post_id: [ - "123", - "321", - ], -} -``` - -*** - -## Soft Delete Records Matching Filters - -```ts -const deletedPosts = await postModuleService.softDeletePosts({ - name: "My Post", -}) -``` - -### Parameters - -To soft delete records matching a set of filters, pass an object of filters as a parameter. - -Learn more about accepted filters in [this documentation](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/tips/filtering/index.html.md). - -### Returns - -The method returns an object, whose keys are of the format `{camel_case_data_model_name}_id`, and their values are arrays of soft-deleted records' IDs. - -For example, the returned object of the above example is: - -```ts -deletedPosts = { - post_id: ["123"], -} -``` - - # Filter Records - Service Factory Reference Many of the service factory's generated methods allow passing filters to perform an operation, such as to update or delete records matching the filters. @@ -31443,6 +31476,7 @@ Download this reference as an OpenApi YAML file. You can import this file to too - [GET /admin/draft-orders](https://docs.medusajs.com/api/admin#draft-orders_getdraftorders) - [POST /admin/draft-orders](https://docs.medusajs.com/api/admin#draft-orders_postdraftorders) - [GET /admin/draft-orders/{id}](https://docs.medusajs.com/api/admin#draft-orders_getdraftordersid) +- [POST /admin/draft-orders/{id}](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersid) - [GET /admin/exchanges](https://docs.medusajs.com/api/admin#exchanges_getexchanges) - [POST /admin/exchanges](https://docs.medusajs.com/api/admin#exchanges_postexchanges) - [GET /admin/exchanges/{id}](https://docs.medusajs.com/api/admin#exchanges_getexchangesid) diff --git a/www/utils/generated/oas-output/base/admin.oas.base.yaml b/www/utils/generated/oas-output/base/admin.oas.base.yaml index 84724e8e30ccf..d112118e86b56 100644 --- a/www/utils/generated/oas-output/base/admin.oas.base.yaml +++ b/www/utils/generated/oas-output/base/admin.oas.base.yaml @@ -106,6 +106,8 @@ tags: externalDocs: description: Learn more about the Order Module url: https://docs.medusajs.com/v2/resources/commerce-modules/order + x-associatedSchema: + $ref: "#/components/schemas/AdminDraftOrder" - name: Exchanges description: > An exchange is the replacement of an item that the customer ordered with diff --git a/www/utils/generated/oas-output/operations/admin/get_admin_draft-orders.ts b/www/utils/generated/oas-output/operations/admin/get_admin_draft-orders.ts index 0d33d01b743db..8d3eb1accde2e 100644 --- a/www/utils/generated/oas-output/operations/admin/get_admin_draft-orders.ts +++ b/www/utils/generated/oas-output/operations/admin/get_admin_draft-orders.ts @@ -1481,33 +1481,7 @@ * content: * application/json: * schema: - * allOf: - * - type: object - * description: The paginated list of draft orders. - * required: - * - limit - * - offset - * - count - * properties: - * limit: - * type: number - * title: limit - * description: The maximum number of items returned. - * offset: - * type: number - * title: offset - * description: The number of items skipped before retrieving the returned items. - * count: - * type: number - * title: count - * description: The total number of items. - * - type: object - * description: The paginated list of draft orders. - * required: - * - draft_orders - * properties: - * draft_orders: - * $ref: "#/components/schemas/AdminOrder" + * $ref: "#/components/schemas/AdminDraftOrderListResponse" * "400": * $ref: "#/components/responses/400_error" * "401": @@ -1520,6 +1494,7 @@ * $ref: "#/components/responses/invalid_request_error" * "500": * $ref: "#/components/responses/500_error" + * x-workflow: getOrdersListWorkflow * */ diff --git a/www/utils/generated/oas-output/operations/admin/get_admin_draft-orders_[id].ts b/www/utils/generated/oas-output/operations/admin/get_admin_draft-orders_[id].ts index 77eb9d4951160..2f34b9b666e57 100644 --- a/www/utils/generated/oas-output/operations/admin/get_admin_draft-orders_[id].ts +++ b/www/utils/generated/oas-output/operations/admin/get_admin_draft-orders_[id].ts @@ -11,6 +11,20 @@ * required: true * schema: * type: string + * - name: fields + * in: query + * description: |- + * Comma-separated fields that should be included in the returned data. + * if a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default fields. + * without prefix it will replace the entire default fields. + * required: false + * schema: + * type: string + * title: fields + * description: Comma-separated fields that should be included in the returned data. If a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default + * fields. Without prefix it will replace the entire default fields. + * externalDocs: + * url: "#select-fields-and-relations" * security: * - api_token: [] * - cookie_auth: [] @@ -42,6 +56,7 @@ * $ref: "#/components/responses/invalid_request_error" * "500": * $ref: "#/components/responses/500_error" + * x-workflow: getOrderDetailWorkflow * */ diff --git a/www/utils/generated/oas-output/operations/admin/post_admin_draft-orders_[id].ts b/www/utils/generated/oas-output/operations/admin/post_admin_draft-orders_[id].ts new file mode 100644 index 0000000000000..8f61d1146de88 --- /dev/null +++ b/www/utils/generated/oas-output/operations/admin/post_admin_draft-orders_[id].ts @@ -0,0 +1,67 @@ +/** + * @oas [post] /admin/draft-orders/{id} + * operationId: PostDraftOrdersId + * summary: Update a Draft Order + * description: Update a draft order's details. + * x-authenticated: true + * parameters: + * - name: id + * in: path + * description: The draft order's ID. + * required: true + * schema: + * type: string + * - name: fields + * in: query + * description: |- + * Comma-separated fields that should be included in the returned data. + * if a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default fields. + * without prefix it will replace the entire default fields. + * required: false + * schema: + * type: string + * title: fields + * description: Comma-separated fields that should be included in the returned data. If a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default + * fields. Without prefix it will replace the entire default fields. + * externalDocs: + * url: "#select-fields-and-relations" + * security: + * - api_token: [] + * - cookie_auth: [] + * - jwt_token: [] + * requestBody: + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/AdminUpdateDraftOrder" + * x-codeSamples: + * - lang: Shell + * label: cURL + * source: |- + * curl -X POST '{backend_url}/admin/draft-orders/{id}' \ + * -H 'Authorization: Bearer {access_token}' + * tags: + * - Draft Orders + * responses: + * "200": + * description: OK + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/AdminDraftOrderResponse" + * "400": + * $ref: "#/components/responses/400_error" + * "401": + * $ref: "#/components/responses/unauthorized" + * "404": + * $ref: "#/components/responses/not_found_error" + * "409": + * $ref: "#/components/responses/invalid_state_error" + * "422": + * $ref: "#/components/responses/invalid_request_error" + * "500": + * $ref: "#/components/responses/500_error" + * x-workflow: updateOrderWorkflow + * +*/ + diff --git a/www/utils/generated/oas-output/operations/admin/post_admin_products_batch.ts b/www/utils/generated/oas-output/operations/admin/post_admin_products_batch.ts index 2c60665d702da..00be15b6b435e 100644 --- a/www/utils/generated/oas-output/operations/admin/post_admin_products_batch.ts +++ b/www/utils/generated/oas-output/operations/admin/post_admin_products_batch.ts @@ -31,7 +31,12 @@ * label: cURL * source: |- * curl -X POST '{backend_url}/admin/products/batch' \ - * -H 'Authorization: Bearer {access_token}' + * -H 'Authorization: Bearer {access_token}' \ + * --data-raw '{ + * "delete": [ + * "prod_123" + * ] + * }' * tags: * - Products * responses: diff --git a/www/utils/generated/oas-output/schemas/AdminCreateProduct.ts b/www/utils/generated/oas-output/schemas/AdminCreateProduct.ts index 5ee4fa54a2049..8256c3978301d 100644 --- a/www/utils/generated/oas-output/schemas/AdminCreateProduct.ts +++ b/www/utils/generated/oas-output/schemas/AdminCreateProduct.ts @@ -5,7 +5,6 @@ * x-schemaName: AdminCreateProduct * required: * - title - * - shipping_profile_id * - options * properties: * title: diff --git a/www/utils/generated/oas-output/schemas/AdminDraftOrder.ts b/www/utils/generated/oas-output/schemas/AdminDraftOrder.ts new file mode 100644 index 0000000000000..c2d2085ed0a8d --- /dev/null +++ b/www/utils/generated/oas-output/schemas/AdminDraftOrder.ts @@ -0,0 +1,248 @@ +/** + * @schema AdminDraftOrder + * type: object + * description: The draft order's details. + * x-schemaName: AdminDraftOrder + * required: + * - payment_collections + * - items + * - shipping_methods + * - status + * - currency_code + * - id + * - version + * - region_id + * - customer_id + * - sales_channel_id + * - email + * - payment_status + * - fulfillment_status + * - summary + * - created_at + * - updated_at + * - original_item_total + * - original_item_subtotal + * - original_item_tax_total + * - item_total + * - item_subtotal + * - item_tax_total + * - original_total + * - original_subtotal + * - original_tax_total + * - total + * - subtotal + * - tax_total + * - discount_total + * - discount_tax_total + * - gift_card_total + * - gift_card_tax_total + * - shipping_total + * - shipping_subtotal + * - shipping_tax_total + * - original_shipping_total + * - original_shipping_subtotal + * - original_shipping_tax_total + * properties: + * payment_collections: + * type: array + * description: The draft order's payment collections. + * items: + * $ref: "#/components/schemas/AdminPaymentCollection" + * fulfillments: + * type: array + * description: The draft order's fulfillments. + * items: + * $ref: "#/components/schemas/AdminOrderFulfillment" + * sales_channel: + * $ref: "#/components/schemas/AdminSalesChannel" + * customer: + * $ref: "#/components/schemas/AdminCustomer" + * shipping_address: + * $ref: "#/components/schemas/AdminOrderAddress" + * billing_address: + * $ref: "#/components/schemas/AdminOrderAddress" + * items: + * type: array + * description: The draft order's items. + * items: + * $ref: "#/components/schemas/AdminOrderLineItem" + * shipping_methods: + * type: array + * description: The draft order's shipping methods. + * items: + * $ref: "#/components/schemas/AdminOrderShippingMethod" + * status: + * type: string + * title: status + * description: The draft order's status. + * currency_code: + * type: string + * title: currency_code + * description: The draft order's currency code. + * example: usd + * id: + * type: string + * title: id + * description: The draft order's ID. + * version: + * type: number + * title: version + * description: The draft order's version. + * region_id: + * type: string + * title: region_id + * description: The ID of the region associated with the draft order. + * customer_id: + * type: string + * title: customer_id + * description: The ID of the customer that the draft order belongs to. + * sales_channel_id: + * type: string + * title: sales_channel_id + * description: The ID of the sales channel that the draft order is placed in. + * email: + * type: string + * title: email + * description: The customer email associated with the draft order. + * format: email + * display_id: + * type: number + * title: display_id + * description: The draft order's display ID. + * payment_status: + * type: string + * description: The draft order's payment status. + * enum: + * - not_paid + * - awaiting + * - authorized + * - partially_authorized + * - canceled + * - captured + * - partially_captured + * - partially_refunded + * - refunded + * - requires_action + * fulfillment_status: + * type: string + * description: The draft order's fulfillment status. + * enum: + * - canceled + * - not_fulfilled + * - partially_fulfilled + * - fulfilled + * - partially_shipped + * - shipped + * - partially_delivered + * - delivered + * transactions: + * type: array + * description: The draft order's transactions. + * items: + * $ref: "#/components/schemas/BaseOrderTransaction" + * summary: + * $ref: "#/components/schemas/BaseOrderSummary" + * metadata: + * type: object + * description: The draft order's metadata, can hold custom key-value pairs. + * created_at: + * type: string + * format: date-time + * title: created_at + * description: The date the draft order was created. + * updated_at: + * type: string + * format: date-time + * title: updated_at + * description: The date the draft order was updated. + * original_item_total: + * type: number + * title: original_item_total + * description: The total of the draft order's items including taxes, excluding promotions. + * original_item_subtotal: + * type: number + * title: original_item_subtotal + * description: The total of the draft order's items excluding taxes, including promotions. + * original_item_tax_total: + * type: number + * title: original_item_tax_total + * description: The tax total of the draft order's items excluding promotions. + * item_total: + * type: number + * title: item_total + * description: The total of the draft order's items including taxes and promotions. + * item_subtotal: + * type: number + * title: item_subtotal + * description: The total of the draft order's items excluding taxes, including promotions. + * item_tax_total: + * type: number + * title: item_tax_total + * description: The tax total of the draft order's items including promotions. + * original_total: + * type: number + * title: original_total + * description: The draft order's total excluding promotions, including taxes. + * original_subtotal: + * type: number + * title: original_subtotal + * description: The draft order's total excluding taxes, including promotions. + * original_tax_total: + * type: number + * title: original_tax_total + * description: The draft order's tax total, excluding promotions. + * total: + * type: number + * title: total + * description: The draft order's total including taxes and promotions. + * subtotal: + * type: number + * title: subtotal + * description: The draft order's total excluding taxes, including promotions. + * tax_total: + * type: number + * title: tax_total + * description: The draft order's tax total including promotions. + * discount_total: + * type: number + * title: discount_total + * description: The draft order's discount or promotions total. + * discount_tax_total: + * type: number + * title: discount_tax_total + * description: The tax total of draft order's discount or promotion. + * gift_card_total: + * type: number + * title: gift_card_total + * description: The draft order's gift card total. + * gift_card_tax_total: + * type: number + * title: gift_card_tax_total + * description: The tax total of the draft order's gift card. + * shipping_total: + * type: number + * title: shipping_total + * description: The draft order's shipping total including taxes and promotions. + * shipping_subtotal: + * type: number + * title: shipping_subtotal + * description: The draft order's shipping total excluding taxes, including promotions. + * shipping_tax_total: + * type: number + * title: shipping_tax_total + * description: The tax total of the draft order's shipping. + * original_shipping_total: + * type: number + * title: original_shipping_total + * description: The draft order's shipping total including taxes, excluding promotions. + * original_shipping_subtotal: + * type: number + * title: original_shipping_subtotal + * description: The draft order's shipping total excluding taxes, including promotions. + * original_shipping_tax_total: + * type: number + * title: original_shipping_tax_total + * description: The tax total of the draft order's shipping excluding promotions. + * +*/ + diff --git a/www/utils/generated/oas-output/schemas/AdminDraftOrderListResponse.ts b/www/utils/generated/oas-output/schemas/AdminDraftOrderListResponse.ts new file mode 100644 index 0000000000000..77fc6c8445263 --- /dev/null +++ b/www/utils/generated/oas-output/schemas/AdminDraftOrderListResponse.ts @@ -0,0 +1,31 @@ +/** + * @schema AdminDraftOrderListResponse + * type: object + * description: The list of draft orders with pagination fields. + * x-schemaName: AdminDraftOrderListResponse + * required: + * - limit + * - offset + * - count + * - draft_orders + * properties: + * limit: + * type: number + * title: limit + * description: The maximum number of items retrieved. + * offset: + * type: number + * title: offset + * description: The number of items skipped before retrieving the returned items. + * count: + * type: number + * title: count + * description: The total count of items available. + * draft_orders: + * type: array + * description: The list of draft orders. + * items: + * $ref: "#/components/schemas/AdminDraftOrder" + * +*/ + diff --git a/www/utils/generated/oas-output/schemas/AdminDraftOrderResponse.ts b/www/utils/generated/oas-output/schemas/AdminDraftOrderResponse.ts index 88ba94cfe2417..d1d667e1e506f 100644 --- a/www/utils/generated/oas-output/schemas/AdminDraftOrderResponse.ts +++ b/www/utils/generated/oas-output/schemas/AdminDraftOrderResponse.ts @@ -7,7 +7,7 @@ * - draft_order * properties: * draft_order: - * $ref: "#/components/schemas/AdminOrder" + * $ref: "#/components/schemas/AdminDraftOrder" * */ diff --git a/www/utils/generated/oas-output/schemas/AdminUpdateDraftOrder.ts b/www/utils/generated/oas-output/schemas/AdminUpdateDraftOrder.ts new file mode 100644 index 0000000000000..213ae8da5f4c5 --- /dev/null +++ b/www/utils/generated/oas-output/schemas/AdminUpdateDraftOrder.ts @@ -0,0 +1,113 @@ +/** + * @schema AdminUpdateDraftOrder + * type: object + * description: The data to update in the draft order. + * x-schemaName: AdminUpdateDraftOrder + * properties: + * email: + * type: string + * title: email + * description: The customer email associated with the draft order. + * format: email + * shipping_address: + * type: object + * description: The draft order's shipping address. + * properties: + * first_name: + * type: string + * title: first_name + * description: The shipping address's first name. + * last_name: + * type: string + * title: last_name + * description: The shipping address's last name. + * phone: + * type: string + * title: phone + * description: The shipping address's phone. + * company: + * type: string + * title: company + * description: The shipping address's company. + * address_1: + * type: string + * title: address_1 + * description: The first address line. + * address_2: + * type: string + * title: address_2 + * description: The second address line. + * city: + * type: string + * title: city + * description: The shipping address's city. + * country_code: + * type: string + * title: country_code + * description: The shipping address's country code. + * example: us + * province: + * type: string + * title: province + * description: The shipping address's province. + * postal_code: + * type: string + * title: postal_code + * description: The shipping address's postal code. + * metadata: + * type: object + * description: The shipping address's metadata, can hold custom key-value pairs. + * billing_address: + * type: object + * description: The draft order's billing address. + * properties: + * first_name: + * type: string + * title: first_name + * description: The billing address's first name. + * last_name: + * type: string + * title: last_name + * description: The billing address's last name. + * phone: + * type: string + * title: phone + * description: The billing address's phone. + * company: + * type: string + * title: company + * description: The billing address's company. + * address_1: + * type: string + * title: address_1 + * description: The first address line. + * address_2: + * type: string + * title: address_2 + * description: The second address line. + * city: + * type: string + * title: city + * description: The billing address's city. + * country_code: + * type: string + * title: country_code + * description: The billing address's country code. + * example: us + * province: + * type: string + * title: province + * description: The billing address's province. + * postal_code: + * type: string + * title: postal_code + * description: The billing address's postal code. + * metadata: + * type: object + * description: The billing address's metadata, can hold custom key-value pairs. + * metadata: + * type: object + * description: The draft order's metadata, can hold custom key-value pairs. + * +*/ + diff --git a/www/utils/generated/oas-output/schemas/BaseOrderSummary.ts b/www/utils/generated/oas-output/schemas/BaseOrderSummary.ts index 9460124a33468..21fdc165bc4b9 100644 --- a/www/utils/generated/oas-output/schemas/BaseOrderSummary.ts +++ b/www/utils/generated/oas-output/schemas/BaseOrderSummary.ts @@ -4,49 +4,14 @@ * description: The order's summary details. * x-schemaName: BaseOrderSummary * required: - * - total - * - subtotal - * - total_tax - * - ordered_total - * - fulfilled_total - * - returned_total - * - return_request_total - * - write_off_total + * - pending_difference + * - current_order_total + * - original_order_total + * - transaction_total * - paid_total * - refunded_total + * - accounting_total * properties: - * total: - * type: number - * title: total - * description: The order's total including taxes and promotions. - * subtotal: - * type: number - * title: subtotal - * description: The order's total excluding taxes, including promotions. - * total_tax: - * type: number - * title: total_tax - * description: The order's total taxes. - * ordered_total: - * type: number - * title: ordered_total - * description: The order's total when it was placed. - * fulfilled_total: - * type: number - * title: fulfilled_total - * description: The total of the fulfilled items of the order. - * returned_total: - * type: number - * title: returned_total - * description: The total of the order's returned items. - * return_request_total: - * type: number - * title: return_request_total - * description: The total of the items requested to be returned. - * write_off_total: - * type: number - * title: write_off_total - * description: The total of the items removed from the order. * paid_total: * type: number * title: paid_total @@ -55,6 +20,26 @@ * type: number * title: refunded_total * description: The total amount refunded. + * pending_difference: + * type: number + * title: pending_difference + * description: The difference pending to be processed. If negative, the customer needs a refund. Otherwise, additional payment is required from the customer. + * current_order_total: + * type: number + * title: current_order_total + * description: The order's current total, could be the total after a change in the order. + * original_order_total: + * type: number + * title: original_order_total + * description: The order's original total. + * transaction_total: + * type: number + * title: transaction_total + * description: The total of the transactions made on the order. + * accounting_total: + * type: number + * title: accounting_total + * description: The order's total without the credit-line total. * */ diff --git a/www/utils/generated/oas-output/schemas/BasePaymentCollection.ts b/www/utils/generated/oas-output/schemas/BasePaymentCollection.ts index be7fe4de2343f..c10f4b0866a0d 100644 --- a/www/utils/generated/oas-output/schemas/BasePaymentCollection.ts +++ b/www/utils/generated/oas-output/schemas/BasePaymentCollection.ts @@ -61,6 +61,8 @@ * - awaiting * - authorized * - partially_authorized + * - completed + * - failed * payment_providers: * type: array * description: The payment provider used to process the collection's payments and sessions. diff --git a/www/utils/generated/oas-output/schemas/BasePaymentSession.ts b/www/utils/generated/oas-output/schemas/BasePaymentSession.ts index b98df6d76bfb8..86ad49078643c 100644 --- a/www/utils/generated/oas-output/schemas/BasePaymentSession.ts +++ b/www/utils/generated/oas-output/schemas/BasePaymentSession.ts @@ -3,6 +3,13 @@ * type: object * description: The payment session's details. * x-schemaName: BasePaymentSession + * required: + * - id + * - amount + * - currency_code + * - provider_id + * - data + * - status * properties: * id: * type: string @@ -25,7 +32,7 @@ * type: object * description: The payment session's data, useful for the payment provider processing the payment. * externalDocs: - * url: https://docs.medusajs.com/v2/resources/commerce-modules/payment/payment-session#data-property + * url: https://docs.medusajs.com/resources/commerce-modules/payment/payment-session#data-property * context: * type: object * description: The context around the payment, such as the customer's details. @@ -36,12 +43,12 @@ * type: string * description: The payment session's status. * enum: + * - error * - authorized - * - captured * - canceled + * - captured * - pending * - requires_more - * - error * authorized_at: * type: string * title: authorized_at @@ -51,13 +58,6 @@ * $ref: "#/components/schemas/BasePaymentCollection" * payment: * $ref: "#/components/schemas/BasePayment" - * required: - * - id - * - amount - * - currency_code - * - provider_id - * - data - * - status * */ diff --git a/www/utils/generated/oas-output/schemas/OrderTransaction.ts b/www/utils/generated/oas-output/schemas/OrderTransaction.ts index e4a2ce5141a67..b733eb537dd16 100644 --- a/www/utils/generated/oas-output/schemas/OrderTransaction.ts +++ b/www/utils/generated/oas-output/schemas/OrderTransaction.ts @@ -6,6 +6,7 @@ * required: * - id * - order_id + * - version * - order * - amount * - currency_code @@ -58,6 +59,10 @@ * description: The date that the transaction was updated. * order: * $ref: "#/components/schemas/Order" + * version: + * type: number + * title: version + * description: The order version that the transaction belongs to. * */ diff --git a/www/utils/generated/oas-output/schemas/StoreCartShippingOption.ts b/www/utils/generated/oas-output/schemas/StoreCartShippingOption.ts index 02e4f5a611505..cdfda520049ca 100644 --- a/www/utils/generated/oas-output/schemas/StoreCartShippingOption.ts +++ b/www/utils/generated/oas-output/schemas/StoreCartShippingOption.ts @@ -16,6 +16,7 @@ * - amount * - prices * - calculated_price + * - insufficient_inventory * properties: * id: * type: string @@ -100,6 +101,10 @@ * $ref: "#/components/schemas/StorePrice" * calculated_price: * $ref: "#/components/schemas/StoreCalculatedPrice" + * insufficient_inventory: + * type: boolean + * title: insufficient_inventory + * description: Whether the shipping option's location doesn't have sufficient quantity for any of the cart's items. * */ diff --git a/www/utils/packages/docs-generator/src/classes/kinds/oas.ts b/www/utils/packages/docs-generator/src/classes/kinds/oas.ts index 878f77c3bd702..e02a8a6f91d89 100644 --- a/www/utils/packages/docs-generator/src/classes/kinds/oas.ts +++ b/www/utils/packages/docs-generator/src/classes/kinds/oas.ts @@ -62,6 +62,17 @@ class OasKindGenerator extends FunctionKindGenerator { public name = "oas" protected allowedKinds: SyntaxKind[] = [ts.SyntaxKind.FunctionDeclaration] private MAX_LEVEL = 7 + readonly METHOD_NAMES = [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "OPTIONS", + "HEAD", + "TRACE", + "CONNECT", + ] readonly REQUEST_TYPE_NAMES = [ "MedusaRequest", "RequestWithContext", @@ -146,8 +157,13 @@ class OasKindGenerator extends FunctionKindGenerator { const functionNode = ts.isFunctionDeclaration(node) ? node : this.extractFunctionNode(node as VariableNode) + const functionName = this.getFunctionName(node as FunctionOrVariableNode) - if (!functionNode || functionNode.parameters.length !== 2) { + if ( + !functionNode || + !this.METHOD_NAMES.includes(functionName) || + functionNode.parameters.length !== 2 + ) { return false } @@ -753,8 +769,10 @@ class OasKindGenerator extends FunctionKindGenerator { } return ( - node as ts.VariableStatement - ).declarationList.declarations[0].name.getText() + ( + node as ts.VariableStatement + ).declarationList?.declarations[0].name.getText() || "" + ) } /**