Skip to content

Releases: medusajs/medusa

v2.3.1

20 Jan 12:52
Compare
Choose a tag to compare

Highlights

Add missing dependency in @medusajs/framework

In @medusajs/framework uninstalled dependencies are directly imported and resolved through transitive dependencies. However, the glob package is not a transitive dependency of any production dependency and hence it fails running the server with NODE_ENV=production.

Bugs

Documentation

Chores

Other Changes

Full Changelog: v2.3.0...v2.3.1

v1.20.11

20 Jan 15:26
Compare
Choose a tag to compare

Fixes

Documentation

Chores

New Contributors

Full Changelog: v1.20.10...v1.20.11

v2.3.0

17 Jan 15:38
Compare
Choose a tag to compare

Highlights

Data migrations script

Some features might require data migrations to ensure new functionality works as expected after an upgrade. Typically, these migrations can be dealt with by database migrations within a module. However, this is not the case when the changes affect the data models in several modules. In those scenarios, the data in multiple modules need to be brought to the desired state.

To support this, we've introduced data migration scripts. They are very similar to regular scripts in the sense, that you have access to the dependency container, i.e. all modules. However, instead of running manually, data migration scripts are run automatically as part of running medusa db:migrate.

How it works

We scour Medusa projects, plugins, and the core for files in the src/migration-scripts and execute them. The files should export a default function. These scripts are only run once, provided they succeed.

The flow is as follows:

  • Find files
  • Ensure the script_migrations table exists–if not, create it
  • Identify pending migration scripts (scripts that have not been ran previously)
  • Acquire a lock on the table
  • Begin loop, for each script
    • Insert migration script in the database
    • Execute script
    • Mark script as finished
  • Release lock

This release focuses on putting the foundation in place for data migrations and does not introduce any scripts.

See #10960 for more.

Cart validation hooks

All Cart workflows have been updated to include a hook to perform validation before executing the operation.

For example, in the completeCartWorkflow you can register a hook like so:

import { completeCartWorkflow } from "@medusajs/medusa/core-flows"

completeCartWorkflow.hooks.validate({ cart } => {
  if (!cart.metadata.approved_at) {
    throw new Error("Cannot complete unapproved cart")
  }
})

Variant Inventory UX upgrade

Warning

Breaking changes

New bulk editors have been added to improve inventory management. One on the product details page, and another on the inventory item list page.

To enable these improvements, we have had to introduce breaking changes to the endpoint updating location levels in batches.

Before

POST /admin/inventory-items/:id/location-levels/batch
{ creates: [ ... ], deletes: [ ... ] }

After

POST /admin/inventory-items/:id/location-levels/batch
{ create: [ ... ], delete: [ ... ], update: [...] }

This brings the endpoint in line with all other batch endpoints.

See #10630 for more.

Payment module changes

Warning

Breaking changes

Data models in the Payment Module have been cleaned up, removing unused fields. This is a breaking change.

The removed fields are region_id from the Payment Collection entity and cart_id, order_id, and customer_id from the payment entity.

See #10987 for more.

Promotion statuses

The process of creating a promotion is sometimes a lengthy one that requires a campaign to be designed, promotion rules to be drafted, approvals to go through, and time to test it out.

This release introduces statuses on promotions to enable these workflows.

Promotions can now be either active, inactive, or draft. All existing promotions will be set to active whileas new promotion will start in a draft state, unless specified otherwise.

See #10950 for more.

Features

Bugs

  • fix(pricing): PriceLists of type Sale should not override lower prices by @kasperkristensen in #10882
  • fix: event-bus-redis processor execute event before subscriber are loaded by @pmc12thsuki in #10823
  • fix(pricing): add null conditions for deleted at during price calculations by @riqwan in #10896
  • fix(modules): Fix miss leading provider resolution error by @adrien2p in #10900
  • fix(types): add missing inventory_items to input of createProductsWorkflow by @shahednasser in #10892
  • fix(orchestration): avoid retry when finished by @carlos-r-l-rodrigues in #10913
  • fix(core-flows): return refunded when all captured payments have been refunded by @riqwan in #10923
  • fix(dashboard, core-flows): improvements to order page on canceled orders by @riqwan in #10888
  • fix(core-flows): missing variable of when condition by @carlos-r-l-rodrigues in #10958
  • fix: Include is default billing and shipping fields in the address re… by @sradevski in #10970
  • chore(dashboard,icons): Update icons and switch icon in CategoryTree by @kasperkristensen in #10961
  • fix(index): Add type casting to raw order by by @adrien2p in #10899
  • fix: Put routes loader error inline by @sradevski in #10912
  • fix(framework): Exclude .d.ts files from the glob search and fix insert query by @adrien2p in #10990
  • fix(dashboard): delete user messages by @fPolic in #11004
  • fix(core-flows,medusa): use deleteRefundReasonsWorkflow in delete /admin/refund-reasons/:id by @shahednasser in #11012

Documentation

Read more

v2.2.0

07 Jan 12:43
38223e5
Compare
Choose a tag to compare

Highlights

Passing custom callback to Oauth providers

Warning

Breaking changes

The callback URL for Oauth providers can now be passed in the payload of the authentication request to support use cases like different login pages based on the actor.

For example, you might have a user/github/callback page and a customer/github/callback page, and where you redirect to would depend on which actor is doing the authentication.

The request would look as follows:

POST http://localhost:9000/customer/auth/github

{ callback_url: "<some url>" }

This change also adds the state parameter as defined in the Oauth protocol.

Passing Query Parameters in Callback Validation

Previously, you only passed the code from the third-party provider (For example, Google) to the callback route. It's now required to pass all query parameters received from the third-party, including state or error.

Required actions

If you have overridden the auth module definition in medusa-config.ts, you will need to pass Modules.CACHE as a dependency, as the state is stored temporarily in cache when carrying out the authentication flow:

module.exports = defineConfig({
 ...
  modules: [
    {
      resolve: "@medusajs/medusa/auth",
      dependencies: [Modules.CACHE, ContainerRegistrationKeys.LOGGER],
      options: {
        providers: [ ... ],
      },
    },
  ],
})

If you have a storefront implementation of third-party authentication, make sure to pass all query parameters to the validate callback route. You can also refer to the following updated documentation pages to learn more:

Custom module types

Container mappings types are generated for modules by inspecting the registered modules. The types are generated and written to your local project in .medusa/types/module-bindings.d.ts. To generate the types, the module needs be functionally correct, registered in medusa-config.ts, and the server needs to be started in development mode.

Once the types are generated, your IDEs intellisense should pick them up, e.g. when creating an API Route:

import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"

export const GET = (req: MedusaRequest, res: MedusaResponse) => {

  const brandModule = req.scope.resolve("brand")

  await brandModule. // <-------------- Autocompletion should appear

  ...
}

If you experience issues with generating types for custom modules, please submit an issue, so we can look into it.

Deprecated APIs

Some APIs have been deprecated and/or renamed as part of a housekeeping task in order to be more consistent with naming across tools and modules.

These are:

  • remoteLink -> link (deprecated and renamed)
  • remoteQueryConfig -> queryConfig (deprecated and renamed)
  • remote-query-entry-points.d.ts -> query-entry-points.d.ts (renaming)

There are no breaking changes in this release, but the deprecated APIs will be removed in a later release, so we recommend updating your code to use the new APIs as soon as possible.

Order cancelation

Canceling an order will also cancel its payments. If the payments are captured, we will attempt to refund an amount equal to the captured amount. If the payments are not captured, they will be canceled immediately.

Other changes

The relationship between stock location and stock location address has been changed to a one-to-one. This change involves a migration, so please apply the latest migrations when upgrading.

Required actions

Run migrations to ensure your server functions as expected after upgrading:

npx medusa db:migrate

Features

  • feat(medusa): Add health endpoint for all running modes of medusa by @sradevski in #10737
  • feat(create-medusa-app): allow passing project name on command line by @shahednasser in #10755
  • feat: deprecate remote link by @thetutlage in #10768
  • feat: deprecate remoteQueryConfig in favor of queryConfig by @thetutlage in #10773
  • feat: generate modules mappings at runtime by @thetutlage in #10791
  • feat(create-medusa-app): improve by adding depth to clone commands by @shahednasser in #10812
  • feat(auth-google,auth-github): Allow passing a custom callbackUrl to … by @sradevski in #10829
  • feat(dashboard,core-flows,types,utils,medusa): Order cancelations will refund payments by @riqwan in #10667

Bugs

  • fix(fulfillment): export schema types by @shahednasser in #10700
  • fix: pluralization of words ending in y, where y follows a vowel by @thetutlage in #10697
  • fix(medusa,types,js-sdk): fix request query parameter types for store product routes by @shahednasser in #10707
  • fix(core-flows): export updateTaxRegionsStep by @shahednasser in #10732
  • fix(pricing): pricing context calculations only takes into account existing rule attributes by @riqwan in #10771
  • Fix/product variants filter by @thetutlage in #10808
  • fix(): Workflow cancellation + gracefully handle non serializable state by @adrien2p in #10674
  • fix(core-flow): invalid update quantity in update line item in cart workflow by @daykrm in #10405
  • fix(promotion): don't evaluate rule condition if conditions to evaluate is empty by @riqwan in #10795
  • fix(stock-location,core-flows,types): updates existing address when updating stock location by @riqwan in #10832
  • feat(core-flows,fulfillment, fulfillment-manual, types): make fulfillment typings more specific by @fPolic in #10677
  • fix(create-medusa-app): add default storefront url to auth CORS by @shahednasser in #10853
  • fix(dashboard): cancel order notifications by @fPolic in #10858

Documentation

Read more

v2.1.3

20 Dec 10:41
Compare
Choose a tag to compare

Highlights

Calculated shipping option price

This release introduces dynamically calculated shipping option prices, which comprise three key changes:

- Admin support for creating dynamically priced options

When creating a shipping option, choose the "calculated" price type. Upon sending the request to create the option, our fulfillment module verifies if the provider and method support calculated prices.

- New storefront endpoint to calculate shipping option prices

To display calculated prices on the storefront, use the endpoint below. Calculated shipping options do not include a price in the regular request to retrieve options for a cart. These are calculated separately–one request for each option.

POST /store/shipping-options/:id/calculate
{ cart_id: "cart_1234", data: { ... } }

Pass custom data in the data property of the payload. The data is passed down to the provider and can be used for the calculation, e.g. distance to destination.

Our Next.js starter has been updated to include this feature. Check it out here.

- Changes to the addShippingMethodToCartWorkflow

When adding a calculated shipping option to a cart, the price is computed dynamically similarly to the calculation endpoint.

Module DML migrations

More core modules have been migrated from MikroORM to DML. These migrations come with non-breaking schema changes, primarily around index renaming and adding default timestamp columns.

Removed orphan deletion

Additionally, we've removed the default orphanRemoval: true configuration from one-to-one relationships. This is a less aggressive approach to cascade removal, meaning that disconnecting entities in such a relation will not delete the "child" of the association.

Required action

Run migrations to ensure your server functions as expected after upgrading:

npx medusa db:migrate

As part of migrating modules to use DML, we've uncovered a range of minor bugs (not affecting end-users) and improvements. You can read more about those in this PR.

Line items with custom prices

This release allows for custom prices on line items by decoupling variants and line items in the cart and order workflows.

Our core API doesn't support custom prices out of the box, however the underlying addToCartWorkflow does.

Here's an example of how to use it in a custom API Route:

// POST /store/carts/:id/custom-line-items
import { addToCartWorkflow } from "@medusajs/medusa/core-flows"

async function thirdPartyCallToComputePrice(item: any) {
  return {
    ...item,
    unit_price: Math.floor(Math.random() * 900) + 100,
  }
}

export const POST = async (req, res) => {
  const { id } = req.params
  const { items } = req.body

  const query = req.scope.resolve("query")

  const itemsWithDynamicPrice = await Promise.all(
    items.map((item) => {
      return thirdPartyCallToComputePrice(item)
    })
  )

  const workflowInput = {
    items: itemsWithDynamicPrice,
    cart_id: id,
  }

  await addToCartWorkflow(req.scope).run({
    input: workflowInput,
  })

  const updatedCart = await query.graph({
    entity: "cart",
    filters: { id },
    fields: ["id", "items.*"],
  })

  res.status(200).json({ cart: updatedCart })
}

Nesting Admin UI Routes under existing domains

This release introduces support for nesting a UI Route under an existing domain in the sidebar instead of in the extensions section.

For example, you can locate Brands under Products:

 import { defineRouteConfig } from "@medusajs/admin-sdk"
 import { Container, Heading } from "@medusajs/ui"

 const NestedProductsPage = () => {
   return (
     <Container className="divide-y p-0">
       <div className="flex items-center justify-between px-6 py-4">
         <Heading level="h1">Nested Products Page</Heading>
       </div>
     </Container>
   )
 }

 export const config = defineRouteConfig({
   label: "Nested Products",
   nested: "/products",
 })

 export default NestedProductsPage

This improvement to our existing UI Routes tooling was a community contribution from @eugenepro2.

Features

  • feat(core-flows,dashboard,js-sdk,medusa,types): support Fulfillment Options by @fPolic in #10622
  • feat: Custom line items by @olivermrbl in #10408
  • feat(order, types): Add Credit Line to order module by @riqwan in #10636
  • feat(core-flows): pass fields variant details when creating fulfillment by @fPolic in #10665

Bugs

  • fix(core-flows): export getItemTaxLinesStep by @shahednasser in #10640
  • fix(medusa): Missing metadata field on order by @olivermrbl in #10651
  • fix(dashboard): order details status by @fPolic in #10650
  • fix(notification): Only use enabled providers for notis by @olivermrbl in #10659
  • fix(core-flows): use useQueryGraphStep instead of useQueryStep by @shahednasser in #10643
  • fix(core-flows): data passed to to fulfillment provider context by @fPolic in #10660
  • fix(core-flows): refresh payment collections upon shipping changes by @riqwan in #10673
  • fix(product): updating collections with products fix by @riqwan in #10668
  • fix(core-flows): select stock locations for reservation from correct SC by @fPolic in #10661

Documentation

Chores

Other Changes

  • Add FilterableRefundReason filter by description and label by @vethan in #10606
  • feat(dashboard): ability to locate new admin route under existing route by @eugenepro2 in #10587

New Contributors

Full Changelog: v2.1.2...v2.1.3

v2.1.2

17 Dec 14:54
632c340
Compare
Choose a tag to compare

Highlights

Breaking changes

The input shape of two core workflows have changed:

  • updateLineItemInCartWorkflow
export interface UpdateLineItemInCartWorkflowInputDTO {
+   cart_id: string
+   item_id: string
-   cart: CartDTO
-   item: CartLineItemDTO
   update: Partial<UpdateLineItemDTO>
 }

  • addToCartWorkflow
export interface AddToCartWorkflowInputDTO {
+   cart_id: string
-   cart: CartDTO
   items: CreateCartCreateLineItemDTO[]
 }

Patches a regression with the Cart Module

Warning

Schema changes

In an earlier release, we migrated the data models of the Cart Module from MikroORM to our DML. As part of that migration, the tax rate column on line item tax lines and shipping method tax lines was mistakenly changed from a numeric to an integer. This led to issues with tax rates with decimal points, as they would get rounded to the nearest integer.

In this release, we are changing the rate column to real (or float() in our DML) to be consistent with how tax rates are generally represented across the codebase.

Required action

Run migrations to ensure your server functions as expected after upgrading:

npx medusa db:migrate

Features

  • refactor: migration stock location module by @thetutlage in #10471
  • refactor: migrate promotion module by @thetutlage in #10410
  • feat(core-flows, dashboard, fulfillment, fulfillment-manual, utils, types): create shipping options with calculated prices by @fPolic in #10495
  • feat: Migrate customer module to DML by @thetutlage in #10499
  • feat(file-s3): Add support for IAM role authentication to file-s3 provider by @sradevski in #10528
  • feat: add support for float properties by @thetutlage in #10551
  • feat(medusa, core-flows, fulfillment): calculate SO price endpoint by @fPolic in #10532
  • Feat/tax dml by @thetutlage in #10525
  • feat(types,fulfillment): ability to delete a canceled fulfillment by @riqwan in #10602
  • feat(core-flows): calculate SO price on cart ops by @fPolic in #10563
  • feat(medusa,pricing): Cart pricing context with customer group by @riqwan in #10579

Bugs

Documentation

Chores

Other Changes

New Contributors

Full Changelog: v2.1.1...v2.1.2

v2.1.1

10 Dec 13:21
Compare
Choose a tag to compare

Highlights

Shipping option rules

We've introduced support for pricing rules on shipping options. Initially, the feature allows you to define prices based on the cart item total. For example, you could have a free shipping option that is only available when the item total is above 100 EUR.

Every time the cart is updated, e.g. with new items, we refresh the shipping methods to ensure the correct price is applied at all times.

You'll find the new setting in the admin dashboard under "Settings > Locations & Shipping > View details > Edit prices (on shipping option)".

New languages

Our admin dashboard has been translated into Japanese.

DML Migrations

Warning

Schema changes

We've migrated our Cart, Sales Channel, API Key, Store, Workflow Engine, and Locking modules to use our Data Model API. These migrations include minor changes to the database schema, primarily around renaming indexes to respect our autogenerated naming conventions.

Read more about each of the schema changes in the PRs below.

Changes to hasOne

As part of our DML work, the behavior of hasOne has been updated in use cases where it is only defined on one side of the relation. In these cases, the mappedBy configuration of the hasOne definition needs to be set to undefined as it is not present on the other model.

For example:

const email = model.define("email", {
   email: model.text(),
   isVerified: model.boolean(),
 })

 const user = model.define("user", {
   id: model.number(),
   username: model.text(),
   email: model.hasOne(() => email, { mappedBy: undefined }), // <------------ Required config
 })

Required actions

Run migrations to ensure your server functions as expected after upgrading to v2.1.0:

npx medusa db:migrate

Features

Bugs

Documentation

Chores

Other Changes

New Contributors

Full Changelog: v2.1.0...v2.1.1

v2.1.0

04 Dec 17:55
a943dfb
Compare
Choose a tag to compare

Highlights

Improved order management

We've improved our order management with support for updating email, shipping address, and billing address.

New languages

Our admin dashboard has been translated into Italian.

Support for check constraint in Data Model API

We've added support for Postgres check constraints in our Data Model API.

For example:

const shippingMethod = model.define('ShippingMethod', {
  amount: model.bigNumber(),
})
.checks([
  (columns) => `${columns.amount} >= 0`
])

DML Migrations

Warning

Schema changes

We've migrated our Pricing, User, and Auth modules to use our Data Model API. These migrations include minor changes to the database schema:

Pricing module

  • Added column operator to price_rule table (defaults to eq)
  • Created index on (operator) in price_rule table
  • Created index on (price_id) in price_rule table
  • Created unique index on (price_id, attribute, operator) in price_rule table
  • Changed type of max_quantity column in price table from numeric to integer
  • Changed type of min_quantity column in price table from numeric to integer

Auth module

  • Added column deleted_at to auth_identity table
  • Added column deleted_at to provider_identity table
  • Created index on (deleted_at) in auth_identity table
  • Created index on (deleted_at) in provider_identity table

User module

  • Renamed unique index on (email) in invite table
  • Renamed unique index on (email) in user table

Required actions

Run migrations to ensure your server functions as expected after upgrading to v2.1.0:

npx medusa db:migrate

Features

  • feat(pricing,utils,types): add operator field to price rule by @riqwan in #10315
  • feat(pricing, types): add price rule operators to price calculations by @riqwan in #10350
  • feat(core-flows,types,medusa): ability to update/create custom shipping prices by @riqwan in #10368
  • feat(core-flows,framework,medusa): list shipping options pass in cart as pricing context by @riqwan in #10374
  • refactor: migrate pricing entities to DML models by @thetutlage in #10335
  • feature: add support for check constraints in DML by @thetutlage in #10391
  • feat(core-flows,medusa,order,types): update orders by @fPolic in #10373
  • feat(admin, js-sdk, types): update order forms by @fPolic in #10418

Bugs

Documentation

Chores

Other Changes

New Contributors

Full Changelog: v2.0.7...v2.1.0

v2.0.7

27 Nov 14:49
Compare
Choose a tag to compare

Highlights

Order transfers

You can now transfer order ownership between customers. We've added UI support in three places:

  • Order details page in Medusa Admin
  • Customer details page (Order list) in Medusa Admin
  • Self-serve in our Next.js and B2B starters

The high-level flow is as follows:

  1. Initiate transfer
POST /admin/orders/:id/transfer  { customer_id: string }

Send a request to generate a transfer token and emit an event with the order ID.

  1. Event subscriber

A subscriber picks up the event and emails the order’s current customer with the token.

  1. Accept Transfer

Customer accepts the order transfer.

POST /store/orders/:id/transfer/accept { token: string }

Product Module DML migration

Warning

Breaking changes (avoidable)

The Product module now uses our DML for data model definitions instead of MikroORM. This migration introduces improvements and fixes but results in breaking changes to some relationship APIs. These can be avoided through explicit configurations.

Required actions

  • Relation ownership on manyToMany relation

Previously, the manyToMany API inferred the owning side of relations based on the order of loading models. If the first-loaded model used mappedBy, it became the owner. Now, ownership is determined by examining both sides of the relation, before defaulting to the old behavior. This change might lead to unexpected behavior, and we therefore recommend explicitly defining the owner.

Explicitly define the owner using joinColumn, inverseJoinColumn, or pivotTable:

const ProductVariant = model
  .define("ProductVariant", {
    id: model.id({ prefix: "variant" }).primaryKey(),
    ...
    options: model.manyToMany(() => ProductOptionValue, {
      pivotTable: "product_variant_option",
      mappedBy: "variants",
      joinColumn: "variant_id",
      inverseJoinColumn: "option_value_id",
    }),
  })
  • Pivot table naming

Aside from the relation ownership, we have also updated how pivot table names are generated.

Before

We used the model names in alphabetical order and pluralized the last one of them.

Now

We use the model's table names in alphabetical order and pluralize the last one of them.

To avoid issues with your project, explicitly set pivotTable in manyToMany relations to match your existing table names:

For example:

const user = model.define('User', () => {
  addresses: model.manyToMany(() => Address, { pivotTable: 'address_users' })
})

New languages

Our admin dashboard has been translated into Brazilian Portuguese, French, Thai, and Spanish.

Features

Bugs

Documentation

Chores

New Contributors

Full Changelog: v2.0.6...v2.0.7

v2.0.6

25 Nov 11:27
Compare
Choose a tag to compare

Highlights

Product image reordering

An issue with duplicated images on product create and update in v2.0.5 has been patched. For information about the feature, see the release notes for v2.0.5.

Bugs

Full Changelog: v2.0.5...v2.0.6