diff --git a/www/apps/book/app/learn/codemods/page.mdx b/www/apps/book/app/learn/codemods/page.mdx new file mode 100644 index 0000000000..cda4e2e29d --- /dev/null +++ b/www/apps/book/app/learn/codemods/page.mdx @@ -0,0 +1,21 @@ +import { ChildDocs } from "docs-ui" + +export const metadata = { + title: `${pageNumber} Medusa Codemods`, +} + +# {metadata.title} + +In this chapter, you'll learn about Medusa codemods and the list of available codemods. + +## What are Codemods? + +Codemods are scripts that help you automate codebase changes. They are especially useful when updating to a new version that requires large changes to your codebase. + +Medusa provides codemods to help you update your codebase. Use these codemods when updating to their respective versions. + +--- + +## List of Medusa Codemods + + \ No newline at end of file diff --git a/www/apps/book/app/learn/codemods/replace-imports/page.mdx b/www/apps/book/app/learn/codemods/replace-imports/page.mdx new file mode 100644 index 0000000000..832ac67080 --- /dev/null +++ b/www/apps/book/app/learn/codemods/replace-imports/page.mdx @@ -0,0 +1,279 @@ +export const metadata = { + title: `${pageNumber} Replace Imports Codemod (v2.11.0+)`, +} + +# {metadata.title} + +In this chapter, you'll learn about the codemod that helps you replace imports in your codebase when upgrading to Medusa v2.11.0. + +## What is the Replace Imports Codemod? + +[Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) optimized the package structure by consolidating several external packages into the `@medusajs/framework` package. + +Previously, you had to install and manage packages related to MikroORM, Awilix, OpenTelemetry, and the `pg` package separately in your Medusa application. Starting with v2.11.0, these packages are included in the `@medusajs/framework` package. + +For example, instead of importing `@mikro-orm/core`, you now import it from `@medusajs/framework/mikro-orm/core`. This applies to all of the following packages: + +- `@mikro-orm/*` packages (for example, `@mikro-orm/core`, `@mikro-orm/migrations`, etc.) -> `@medusajs/framework/mikro-orm/{subpath}` +- `awilix` -> `@medusajs/framework/awilix` +- `pg` -> `@medusajs/framework/pg` +- `@opentelemetry/instrumentation-pg` -> `@medusajs/framework/opentelemetry/instrumentation-pg` +- `@opentelemetry/resources` -> `@medusajs/framework/opentelemetry/resources` +- `@opentelemetry/sdk-node` -> `@medusajs/framework/opentelemetry/sdk-node` +- `@opentelemetry/sdk-trace-node` -> `@medusajs/framework/opentelemetry/sdk-trace-node` + +To help you update your codebase to reflect these changes, Medusa provides a codemod that automatically replaces imports of these packages throughout your codebase. + +--- + +## Using the Replace Imports Codemod + +To use the replace imports codemod, create the file `replace-imports.js` in the root of your Medusa application with the following content: + +```js +#!/usr/bin/env node + +const fs = require("fs") +const path = require("path") +const { execSync } = require("child_process") + +/** + * Script to replace imports and require statements from mikro-orm/{subpath}, awilix, and pg + * to their @medusajs/framework equivalents + */ + +// Define the replacement mappings +const replacements = [ + // MikroORM imports - replace mikro-orm/{subpath} with @medusajs/framework/mikro-orm/{subpath} + { + pattern: /from\s+['"]@?mikro-orm\/([^'"]+)['"]/g, + // eslint-disable-next-line quotes + replacement: 'from "@medusajs/framework/mikro-orm/$1"', + }, + // Awilix imports - replace awilix with @medusajs/framework/awilix + { + pattern: /from\s+['"]awilix['"]/g, + // eslint-disable-next-line quotes + replacement: 'from "@medusajs/framework/awilix"', + }, + // PG imports - replace pg with @medusajs/framework/pg + { + pattern: /from\s+['"]pg['"]/g, + // eslint-disable-next-line quotes + replacement: 'from "@medusajs/framework/pg"', + }, + // OpenTelemetry imports - replace @opentelemetry/instrumentation-pg, @opentelemetry/resources, + // @opentelemetry/sdk-node, and @opentelemetry/sdk-trace-node with @medusajs/framework/opentelemetry/{subpath} + { + pattern: /from\s+['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]/g, + // eslint-disable-next-line quotes + replacement: 'from "@medusajs/framework/opentelemetry/$1"', + }, + // MikroORM require statements - replace require('@?mikro-orm/{subpath}') with require('@medusajs/framework/mikro-orm/{subpath}') + { + pattern: /require\s*\(\s*['"]@?mikro-orm\/([^'"]+)['"]\s*\)/g, + // eslint-disable-next-line quotes + replacement: 'require("@medusajs/framework/mikro-orm/$1")', + }, + // Awilix require statements - replace require('awilix') with require('@medusajs/framework/awilix') + { + pattern: /require\s*\(\s*['"]awilix['"]\s*\)/g, + // eslint-disable-next-line quotes + replacement: 'require("@medusajs/framework/awilix")', + }, + // PG require statements - replace require('pg') with require('@medusajs/framework/pg') + { + pattern: /require\s*\(\s*['"]pg['"]\s*\)/g, + // eslint-disable-next-line quotes + replacement: 'require("@medusajs/framework/pg")', + }, + // OpenTelemetry require statements - replace require('@opentelemetry/instrumentation-pg'), + // require('@opentelemetry/resources'), require('@opentelemetry/sdk-node'), and + // require('@opentelemetry/sdk-trace-node') with require('@medusajs/framework/opentelemetry/{subpath}') + { + pattern: /require\s*\(\s*['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]\s*\)/g, + // eslint-disable-next-line quotes + replacement: 'require("@medusajs/framework/opentelemetry/$1")', + }, +] + +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, "utf8") + let modifiedContent = content + let wasModified = false + + replacements.forEach(({ pattern, replacement }) => { + const newContent = modifiedContent.replace(pattern, replacement) + if (newContent !== modifiedContent) { + wasModified = true + modifiedContent = newContent + } + }) + + if (wasModified) { + fs.writeFileSync(filePath, modifiedContent) + console.log(`βœ“ Updated: ${filePath}`) + return true + } + + return false + } catch (error) { + console.error(`βœ— Error processing ${filePath}:`, error.message) + return false + } +} + +function getTargetFiles() { + try { + // Get the current script's filename to exclude it from processing + const currentScript = path.basename(__filename) + + // Find TypeScript/JavaScript files, excluding common directories that typically don't contain target imports + const findCommand = `find . -name node_modules -prune -o -name .git -prune -o -name dist -prune -o -name build -prune -o -name coverage -prune -o -name "*.ts" -print -o -name "*.js" -print -o -name "*.tsx" -print -o -name "*.jsx" -print` + const files = execSync(findCommand, { + encoding: "utf8", + maxBuffer: 50 * 1024 * 1024, // 50MB buffer + }) + .split("\n") + .filter((line) => line.trim()) + + console.log(files) + + const targetFiles = [] + let processedCount = 0 + + console.log(`πŸ“„ Scanning ${files.length} files for target imports and require statements...`) + + for (const file of files) { + try { + // Skip the current script file + const fileName = path.basename(file) + if (fileName === currentScript) { + processedCount++ + continue + } + const content = fs.readFileSync(file, "utf8") + if ( + /from\s+['"]@?mikro-orm\//.test(content) || + /from\s+['"]awilix['"]/.test(content) || + /from\s+['"]pg['"]/.test(content) || + /require\s*\(\s*['"]@?mikro-orm\//.test(content) || + /require\s*\(\s*['"]awilix['"]/.test(content) || + /require\s*\(\s*['"]pg['"]/.test(content) + ) { + targetFiles.push(file.startsWith("./") ? file.slice(2) : file) + } + processedCount++ + if (processedCount % 100 === 0) { + process.stdout.write( + `\rπŸ“„ Processed ${processedCount}/${files.length} files...` + ) + } + } catch (fileError) { + // Skip files that can't be read + continue + } + } + + if (processedCount > 0) { + console.log(`\rπŸ“„ Processed ${processedCount} files. `) + } + + return targetFiles + } catch (error) { + console.error("Error finding target files:", error.message) + return [] + } +} + +function main() { + console.log("πŸ”„ Finding files with target imports and require statements...") + + const targetFiles = getTargetFiles() + + if (targetFiles.length === 0) { + console.log("ℹ️ No files found with target imports or require statements.") + return + } + + console.log(`πŸ“ Found ${targetFiles.length} files to process`) + + let modifiedCount = 0 + let errorCount = 0 + + targetFiles.forEach((filePath) => { + const fullPath = path.resolve(filePath) + if (fs.existsSync(fullPath)) { + if (processFile(fullPath)) { + modifiedCount++ + } + } else { + console.warn(`⚠️ File not found: ${filePath}`) + errorCount++ + } + }) + + console.log("\nπŸ“Š Summary:") + console.log(` Files processed: ${targetFiles.length}`) + console.log(` Files modified: ${modifiedCount}`) + console.log(` Errors: ${errorCount}`) + + if (modifiedCount > 0) { + console.log("\nβœ… Import replacement completed successfully!") + console.log("\nπŸ’‘ Next steps:") + console.log(" 1. Review the changes with: git diff") + console.log(" 2. Run your tests to ensure everything works correctly") + console.log(" 3. Commit the changes if you're satisfied") + } else { + console.log( + "\nβœ… No modifications needed - all imports are already correct!" + ) + } +} + +// Run if called directly +if (require.main === module) { + main() +} + +module.exports = { processFile, getTargetFiles, main } +``` + +This script scans your project for files that import from `mikro-orm/{subpath}`, `awilix`, or `pg`, and replaces those imports with their new equivalents from `@medusajs/framework`. It handles both ES module `import` statements and CommonJS `require` statements in JavaScript and TypeScript files. + +Next, run the following command in your terminal to make the script executable: + + + +You can run the script using `node` without changing permissions. + + + +```bash +chmod +x replace-imports.js +``` + +Finally, execute the script with the following command: + +```bash +node replace-imports.js +``` + +This will scan your project files, apply the necessary import replacements, and provide a summary of the changes made. + +--- + +## Next Steps + +After running the codemod, review the changes made to your codebase. You can use `git diff` to see the modifications. Additionally, run your tests to ensure everything works as expected. + +If everything is working correctly, you can remove the `replace-imports.js` file from your project. You can also remove the following packages from your `package.json`, as they're now included in the `@medusajs/framework` package: + +- `@mikro-orm/*` packages (for example, `@mikro-orm/core`, `@mikro-orm/migrations`, etc.) +- `awilix` +- `pg` +- `@opentelemetry/instrumentation-pg` +- `@opentelemetry/resources` +- `@opentelemetry/sdk-node` +- `@opentelemetry/sdk-trace-node` diff --git a/www/apps/book/app/learn/debugging-and-testing/instrumentation/page.mdx b/www/apps/book/app/learn/debugging-and-testing/instrumentation/page.mdx index 15e8f0ce5d..1537975425 100644 --- a/www/apps/book/app/learn/debugging-and-testing/instrumentation/page.mdx +++ b/www/apps/book/app/learn/debugging-and-testing/instrumentation/page.mdx @@ -40,13 +40,13 @@ Medusa uses [OpenTelemetry](https://opentelemetry.io/) for instrumentation and r ### Install Dependencies -Start by installing the following OpenTelemetry dependencies in your Medusa project: + -```bash npm2yarn -npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg -``` +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), OpenTelemetry dependencies are installed by default in new Medusa projects. If you're using an older version of Medusa, you need to install the `@opentelemetry/sdk-node`, `@opentelemetry/resources`, `@opentelemetry/sdk-trace-node`, and `@opentelemetry/instrumentation-pg` dependencies. -Also, install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies: + + +Before you start, you must install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies: ```bash npm2yarn npm install @opentelemetry/exporter-zipkin diff --git a/www/apps/book/app/learn/debugging-and-testing/testing-tools/page.mdx b/www/apps/book/app/learn/debugging-and-testing/testing-tools/page.mdx index 5da2bf1003..757f91b425 100644 --- a/www/apps/book/app/learn/debugging-and-testing/testing-tools/page.mdx +++ b/www/apps/book/app/learn/debugging-and-testing/testing-tools/page.mdx @@ -63,8 +63,14 @@ if (process.env.TEST_TYPE === "integration:http") { Next, create the `integration-tests/setup.js` file with the following content: + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the require statement to `@mikro-orm/core`. + + + ```js title="integration-tests/setup.js" -const { MetadataStorage } = require("@mikro-orm/core") +const { MetadataStorage } = require("@medusajs/framework/@mikro-orm/core") MetadataStorage.clear() ``` diff --git a/www/apps/book/app/learn/fundamentals/admin/environment-variables/page.mdx b/www/apps/book/app/learn/fundamentals/admin/environment-variables/page.mdx index d36e3aa6c4..8a20cfeda2 100644 --- a/www/apps/book/app/learn/fundamentals/admin/environment-variables/page.mdx +++ b/www/apps/book/app/learn/fundamentals/admin/environment-variables/page.mdx @@ -98,9 +98,48 @@ For example, the `VITE_MY_API_KEY` environment variable in the example above wil ## Environment Variables in Plugins -As explained in the [previous section](#environment-variables-in-production), environment variables are inlined into the build. This presents a limitation for plugins, where you can't use environment variables. + -Instead, only the following global variable is available in plugins: +Environment variable support in plugins is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Refer to the [Medusa versions prior to v2.11.0](#for-medusa-versions-prior-to-v2110) section for more details if you're using an earlier version. + + + +For plugins, you can use environment variables without a prefix. Then, Medusa applications that use the plugin can set the environment variable with the `PLUGIN_` prefix. + +For example, you can create a widget in your plugin that uses the `MY_API_KEY` environment variable: + +```tsx highlights={[["8"]]} +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Container, Heading } from "@medusajs/ui" + +const ProductWidget = () => { + return ( + +
+ API Key: {import.meta.env.MY_API_KEY} +
+
+ ) +} + +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) + +export default ProductWidget +``` + +Then, in the Medusa application that uses the plugin, set the environment variable with the `PLUGIN_` prefix: + +```bash +PLUGIN_MY_API_KEY=sk_123 +``` + +The `MY_API_KEY` environment variable in the plugin will be replaced with the value of `PLUGIN_MY_API_KEY` during the build process of the Medusa application. + +### Global Variables in Plugins + +Plugins also have the following global variables available: - `__BACKEND_URL__`: The URL of the Medusa backend, as set in the [Medusa configurations](../../../configurations/medusa-config/page.mdx#backendurl). - `__BASE__`: The base path of the Medusa Admin. (For example, `/app`). @@ -137,4 +176,14 @@ To fix possible type errors, create the file `src/admin/vite-env.d.ts` and add t declare const __BACKEND_URL__: string declare const __BASE__: string declare const __STOREFRONT_URL__: string -``` \ No newline at end of file +``` + +### For Medusa versions prior to v2.11.0 + +
+ +As explained in the [Environment Variables in Production section](#environment-variables-in-production), environment variables are inlined into the build. This presents a limitation for plugins, where you can't use environment variables. + +Instead, you can use the [Plugin Global Variables](#global-variables-in-plugins) described above to access the backend URL, base path, and storefront URL. + +
\ No newline at end of file diff --git a/www/apps/book/app/learn/fundamentals/data-models/page.mdx b/www/apps/book/app/learn/fundamentals/data-models/page.mdx index 4228f80acd..1677837c7a 100644 --- a/www/apps/book/app/learn/fundamentals/data-models/page.mdx +++ b/www/apps/book/app/learn/fundamentals/data-models/page.mdx @@ -63,8 +63,14 @@ npx medusa db:generate blog 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: + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`. + + + ```ts -import { Migration } from "@mikro-orm/migrations" +import { Migration } from "@medusajs/framework/@mikro-orm/migrations" export class Migration20241121103722 extends Migration { diff --git a/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx b/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx index 5c1c5ca0de..768393bda2 100644 --- a/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx +++ b/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx @@ -37,8 +37,14 @@ You can also write migrations manually. To do that, create a file in the `migrat For example: + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`. + + + ```ts title="src/modules/blog/migrations/Migration202507021059_create_author.ts" -import { Migration } from "@mikro-orm/migrations" +import { Migration } from "@medusajs/framework/@mikro-orm/migrations" export class Migration202507021059 extends Migration { @@ -53,7 +59,7 @@ export class Migration202507021059 extends Migration { } ``` -The migration class in the file extends the `Migration` class imported from `@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax. +The migration class in the file extends the `Migration` class imported from `@medusajs/framework/@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax. In the example above, the `up` method creates the table `author`, and the `down` method drops the table if the migration is reverted. diff --git a/www/apps/book/app/learn/fundamentals/modules/db-operations/page.mdx b/www/apps/book/app/learn/fundamentals/modules/db-operations/page.mdx index c9826c5503..f7415b685d 100644 --- a/www/apps/book/app/learn/fundamentals/modules/db-operations/page.mdx +++ b/www/apps/book/app/learn/fundamentals/modules/db-operations/page.mdx @@ -27,6 +27,12 @@ So, to run database queries in a service: For example, in your service, add the following methods: + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + export const methodsHighlight = [ ["13", "getCount", "Retrieves the number of records in `my_custom` using the `count` method."], ["20", "getCountSql", "Retrieves the number of records in `my_custom` using the `execute` method."] @@ -39,7 +45,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -66,7 +72,7 @@ class BlogModuleService { You add two methods `getCount` and `getCountSql` that have the `InjectManager` decorator. Each of the methods also accept the `sharedContext` parameter which has the `MedusaContext` decorator. -The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the @mikro-orm/knex package](https://mikro-orm.io/api/knex/class/EntityManager). +The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the `@medusajs/framework/@mikro-orm/knex` package](https://mikro-orm.io/api/knex/class/EntityManager). You use the manager in the `getCount` method to retrieve the number of records in a table, and in the `getCountSql` to run a PostgreSQL query that retrieves the count. @@ -113,7 +119,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -174,7 +180,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -221,7 +227,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -380,7 +386,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -512,7 +518,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -582,7 +588,7 @@ The second parameter of the `baseRepository_.transaction` method is an object of ```ts highlights={[["24"]]} // other imports... -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import { InjectTransactionManager, MedusaContext, @@ -625,8 +631,8 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" -import { IsolationLevel } from "@mikro-orm/core" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" +import { IsolationLevel } from "@medusajs/framework/@mikro-orm/core" class BlogModuleService { // ... @@ -660,7 +666,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... diff --git a/www/apps/book/app/learn/fundamentals/modules/loaders/page.mdx b/www/apps/book/app/learn/fundamentals/modules/loaders/page.mdx index 8a7bc8373f..6c642f467f 100644 --- a/www/apps/book/app/learn/fundamentals/modules/loaders/page.mdx +++ b/www/apps/book/app/learn/fundamentals/modules/loaders/page.mdx @@ -152,6 +152,12 @@ Consider your have a MongoDB module that allows you to perform operations on a M To connect to the database, you create the following loader in your module: + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), Awilix dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `awilix`. + + + export const loaderHighlights = [ ["5", "ModuleOptions", "Define a type for expected options."], ["13", "ModuleOptions", "Pass the option type as a type argument to `LoaderOptions`."], @@ -163,7 +169,7 @@ export const loaderHighlights = [ ```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights} import { LoaderOptions } from "@medusajs/framework/types" -import { asValue } from "awilix" +import { asValue } from "@medusajs/framework/awilix" import { MongoClient } from "mongodb" type ModuleOptions = { @@ -233,7 +239,7 @@ In the loader, you check first that these options are set before proceeding. The 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. +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 [`@medusajs/framework/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 diff --git a/www/apps/book/app/learn/fundamentals/modules/page.mdx b/www/apps/book/app/learn/fundamentals/modules/page.mdx index 9de5e12117..024a243bb4 100644 --- a/www/apps/book/app/learn/fundamentals/modules/page.mdx +++ b/www/apps/book/app/learn/fundamentals/modules/page.mdx @@ -195,8 +195,14 @@ npx medusa db:generate blog 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: + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`. + + + ```ts -import { Migration } from "@mikro-orm/migrations" +import { Migration } from "@medusajs/framework/@mikro-orm/migrations" export class Migration20241121103722 extends Migration { diff --git a/www/apps/book/app/learn/introduction/from-v1-to-v2/page.mdx b/www/apps/book/app/learn/introduction/from-v1-to-v2/page.mdx index dcd657c552..4506bf8e22 100644 --- a/www/apps/book/app/learn/introduction/from-v1-to-v2/page.mdx +++ b/www/apps/book/app/learn/introduction/from-v1-to-v2/page.mdx @@ -70,17 +70,10 @@ A basic v2 project has the following dependencies in `package.json`: "@medusajs/admin-sdk": "2.8.2", "@medusajs/cli": "2.8.2", "@medusajs/framework": "2.8.2", - "@medusajs/medusa": "2.8.2", - "@mikro-orm/core": "6.4.3", - "@mikro-orm/knex": "6.4.3", - "@mikro-orm/migrations": "6.4.3", - "@mikro-orm/postgresql": "6.4.3", - "awilix": "^8.0.1", - "pg": "^8.13.0" + "@medusajs/medusa": "2.8.2" }, "devDependencies": { "@medusajs/test-utils": "2.8.2", - "@mikro-orm/cli": "6.4.3", "@swc/core": "1.5.7", "@swc/jest": "^0.2.36", "@types/jest": "^29.5.13", @@ -107,25 +100,15 @@ The main changes are: - `@medusajs/framework` - `@medusajs/medusa` - `@medusajs/test-utils` (as a dev dependency) -- You need to install the following extra packages: - - Database packages: - - `@mikro-orm/core@6.4.3` - - `@mikro-orm/knex@6.4.3` - - `@mikro-orm/migrations@6.4.3` - - `@mikro-orm/postgresql@6.4.3` - - `@mikro-orm/cli@6.4.3` (as a dev dependency) - - `pg^8.13.0` - - Framework packages: - - `awilix@^8.0.1` - - Development and Testing packages: - - `@swc/core@1.5.7` - - `@swc/jest@^0.2.36` - - `@types/node@^20.0.0` - - `jest@^29.7.0` - - `ts-node@^10.9.2` - - `typescript@^5.6.2` - - `vite@^5.2.11` - - `yalc@^1.0.0-pre.53` +- You need to install the following extra packages for development and testing: + - `@swc/core@1.5.7` + - `@swc/jest@^0.2.36` + - `@types/node@^20.0.0` + - `jest@^29.7.0` + - `ts-node@^10.9.2` + - `typescript@^5.6.2` + - `vite@^5.2.11` + - `yalc@^1.0.0-pre.53` - Other packages, such as `@types/react` and `@types/react-dom`, are necessary for admin development and TypeScript support. diff --git a/www/apps/book/app/learn/production/worker-mode/page.mdx b/www/apps/book/app/learn/production/worker-mode/page.mdx index 40a095895c..a60d3e8b31 100644 --- a/www/apps/book/app/learn/production/worker-mode/page.mdx +++ b/www/apps/book/app/learn/production/worker-mode/page.mdx @@ -10,7 +10,14 @@ In this chapter, you'll learn about the different modes of running a Medusa inst ## What is Worker Mode? -By default, the Medusa application runs both the server, which handles all incoming requests, and the worker, which processes background tasks, in a single process. While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive. +By default, the Medusa application runs in `shared` mode, which runs: + +- `server`: the application server that handles incoming requests to the application's API routes. +- `worker`: the worker that processes background tasks. This includes scheduled jobs and subscribers. + +While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive. + +### Worker Mode in Production In a production environment, you should deploy two separate instances of your Medusa application: @@ -104,3 +111,27 @@ ADMIN_DISABLED=true + +--- + +## Dividing Resources in Cluster Mode + + + +The `--servers` and `--workers` options were introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + + + +When running Medusa in [cluster mode](!resources!/medusa-cli/commands/start#starting-medusa-in-cluster-mode), you can specify the number or percentage of instances that are servers or workers by passing the `--servers` and `--workers` options: + +```bash +npx medusa start --cluster 4 --servers 25% --workers 75% # Use 4 CPU cores, with 25% as servers and 75% as workers +npx medusa start --cluster 4 --servers 1 --workers 3 # Use 4 CPU cores, with 1 as server and 3 as workers +npx medusa start --cluster 4 --servers 1 --workers 1 # Use 4 CPU cores, with 1 as server and 1 as worker (the remaining 2 will run in shared mode) +``` + +In the above snippet you can see the following examples: + +- In the first example, 25% of the instances (1 out of 4) will run as servers, and 75% (3 out of 4) will run as workers. +- In the second example, 1 instance will run as a server, and 3 instances will run as workers. +- In the third example, 1 instance will run as a server, and 1 instance will run as a worker. The remaining 2 instances will run in shared mode \ No newline at end of file diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index 5a7ce6aa49..c9a40e9b68 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -18,9 +18,9 @@ export const generatedEditDates = { "app/learn/fundamentals/events-and-subscribers/page.mdx": "2025-10-16T09:36:04.864Z", "app/learn/fundamentals/modules/container/page.mdx": "2025-07-31T14:24:04.087Z", "app/learn/fundamentals/workflows/execute-another-workflow/page.mdx": "2025-08-01T07:28:51.036Z", - "app/learn/fundamentals/modules/loaders/page.mdx": "2025-06-16T13:34:16.462Z", + "app/learn/fundamentals/modules/loaders/page.mdx": "2025-10-09T11:41:31.724Z", "app/learn/fundamentals/admin/widgets/page.mdx": "2025-07-25T15:08:07.035Z", - "app/learn/fundamentals/data-models/page.mdx": "2025-03-18T07:55:56.252Z", + "app/learn/fundamentals/data-models/page.mdx": "2025-10-09T11:39:30.944Z", "app/learn/fundamentals/modules/remote-link/page.mdx": "2024-09-30T08:43:53.127Z", "app/learn/fundamentals/api-routes/protected-routes/page.mdx": "2025-06-19T16:04:36.064Z", "app/learn/fundamentals/workflows/add-workflow-hook/page.mdx": "2025-07-18T11:33:15.959Z", @@ -32,7 +32,7 @@ export const generatedEditDates = { "app/learn/fundamentals/admin/page.mdx": "2025-07-25T12:46:15.466Z", "app/learn/fundamentals/workflows/long-running-workflow/page.mdx": "2025-08-01T07:16:21.736Z", "app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2025-08-01T13:11:18.823Z", - "app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-07-25T13:53:00.692Z", + "app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-10-09T11:39:07.940Z", "app/learn/fundamentals/data-models/manage-relationships/page.mdx": "2025-04-25T14:16:41.124Z", "app/learn/fundamentals/modules/remote-query/page.mdx": "2024-07-21T21:20:24+02:00", "app/learn/fundamentals/modules/options/page.mdx": "2025-03-18T15:12:34.510Z", @@ -53,7 +53,7 @@ export const generatedEditDates = { "app/learn/debugging-and-testing/testing-tools/integration-tests/api-routes/page.mdx": "2025-09-02T08:36:12.714Z", "app/learn/debugging-and-testing/testing-tools/integration-tests/page.mdx": "2024-12-09T15:52:01.019Z", "app/learn/debugging-and-testing/testing-tools/integration-tests/workflows/page.mdx": "2025-07-30T13:43:44.636Z", - "app/learn/debugging-and-testing/testing-tools/page.mdx": "2025-07-23T15:32:18.008Z", + "app/learn/debugging-and-testing/testing-tools/page.mdx": "2025-10-09T11:38:33.099Z", "app/learn/debugging-and-testing/testing-tools/unit-tests/module-example/page.mdx": "2024-09-02T11:04:27.232Z", "app/learn/debugging-and-testing/testing-tools/unit-tests/page.mdx": "2024-09-02T11:03:26.997Z", "app/learn/fundamentals/modules/service-constraints/page.mdx": "2025-03-18T15:12:46.006Z", @@ -66,10 +66,10 @@ export const generatedEditDates = { "app/learn/fundamentals/module-links/directions/page.mdx": "2025-03-17T12:52:06.161Z", "app/learn/fundamentals/module-links/page.mdx": "2025-04-17T08:50:17.036Z", "app/learn/fundamentals/module-links/query/page.mdx": "2025-08-15T12:06:30.572Z", - "app/learn/fundamentals/modules/db-operations/page.mdx": "2025-04-25T14:26:25.000Z", + "app/learn/fundamentals/modules/db-operations/page.mdx": "2025-10-09T11:43:28.746Z", "app/learn/fundamentals/modules/multiple-services/page.mdx": "2025-03-18T15:11:44.632Z", - "app/learn/fundamentals/modules/page.mdx": "2025-07-18T15:31:32.371Z", - "app/learn/debugging-and-testing/instrumentation/page.mdx": "2025-06-16T10:40:52.922Z", + "app/learn/fundamentals/modules/page.mdx": "2025-10-09T11:41:57.515Z", + "app/learn/debugging-and-testing/instrumentation/page.mdx": "2025-10-09T11:37:32.815Z", "app/learn/fundamentals/api-routes/additional-data/page.mdx": "2025-04-17T08:50:17.036Z", "app/learn/fundamentals/workflows/variable-manipulation/page.mdx": "2025-04-24T13:14:43.967Z", "app/learn/customization/custom-features/api-route/page.mdx": "2025-10-16T11:23:11.195Z", @@ -113,7 +113,7 @@ export const generatedEditDates = { "app/learn/resources/usage/page.mdx": "2025-02-26T13:35:34.824Z", "app/learn/configurations/medusa-config/page.mdx": "2025-09-30T06:04:15.705Z", "app/learn/configurations/ts-aliases/page.mdx": "2025-07-23T15:32:18.008Z", - "app/learn/production/worker-mode/page.mdx": "2025-07-18T15:19:45.352Z", + "app/learn/production/worker-mode/page.mdx": "2025-10-13T10:33:27.403Z", "app/learn/fundamentals/module-links/read-only/page.mdx": "2025-10-15T15:42:22.610Z", "app/learn/fundamentals/data-models/properties/page.mdx": "2025-10-15T05:36:40.576Z", "app/learn/fundamentals/framework/page.mdx": "2025-06-26T14:26:22.120Z", @@ -125,12 +125,14 @@ export const generatedEditDates = { "app/learn/introduction/build-with-llms-ai/page.mdx": "2025-10-02T15:10:49.394Z", "app/learn/installation/docker/page.mdx": "2025-07-23T15:34:18.530Z", "app/learn/fundamentals/generated-types/page.mdx": "2025-07-25T13:17:35.319Z", - "app/learn/introduction/from-v1-to-v2/page.mdx": "2025-09-01T06:34:41.179Z", + "app/learn/introduction/from-v1-to-v2/page.mdx": "2025-09-29T15:33:38.811Z", "app/learn/debugging-and-testing/debug-workflows/page.mdx": "2025-07-30T13:45:14.117Z", "app/learn/fundamentals/data-models/json-properties/page.mdx": "2025-07-31T14:25:01.268Z", "app/learn/debugging-and-testing/logging/custom-logger/page.mdx": "2025-08-28T15:37:07.328Z", "app/learn/fundamentals/scheduled-jobs/interval/page.mdx": "2025-09-02T08:36:12.714Z", "app/learn/debugging-and-testing/feature-flags/create/page.mdx": "2025-09-02T08:36:12.714Z", "app/learn/debugging-and-testing/feature-flags/page.mdx": "2025-09-02T08:36:12.714Z", - "app/learn/fundamentals/workflows/locks/page.mdx": "2025-09-15T09:37:00.808Z" + "app/learn/fundamentals/workflows/locks/page.mdx": "2025-09-15T09:37:00.808Z", + "app/learn/codemods/page.mdx": "2025-09-29T15:40:03.620Z", + "app/learn/codemods/replace-imports/page.mdx": "2025-10-09T11:37:44.754Z" } \ No newline at end of file diff --git a/www/apps/book/generated/sidebar.mjs b/www/apps/book/generated/sidebar.mjs index 29bfa27d95..d07fb5d9fc 100644 --- a/www/apps/book/generated/sidebar.mjs +++ b/www/apps/book/generated/sidebar.mjs @@ -1290,9 +1290,9 @@ export const generatedSidebars = [ "isPathHref": true, "type": "link", "path": "/learn/production/worker-mode", - "title": "Worker Mode", + "title": "Worker Modes", "children": [], - "chapterTitle": "8.2. Worker Mode", + "chapterTitle": "8.2. Worker Modes", "number": "8.2." }, { @@ -1345,6 +1345,27 @@ export const generatedSidebars = [ "children": [], "chapterTitle": "9.2. Release Notes", "number": "9.2." + }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/learn/codemods", + "title": "Codemods", + "children": [ + { + "loaded": true, + "isPathHref": true, + "type": "link", + "title": "Replace Imports (v2.11.0+)", + "path": "/learn/codemods/replace-imports", + "children": [], + "chapterTitle": "9.3.1. Replace Imports (v2.11.0+)", + "number": "9.3.1." + } + ], + "chapterTitle": "9.3. Codemods", + "number": "9.3." } ], "chapterTitle": "9. Upgrade", diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index d9fd728d35..3a00c44ca7 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -139,6 +139,294 @@ npx medusa build --admin-only The next chapter covers how to deploy the production build. +# Medusa Codemods + +In this chapter, you'll learn about Medusa codemods and the list of available codemods. + +## What are Codemods? + +Codemods are scripts that help you automate codebase changes. They are especially useful when updating to a new version that requires large changes to your codebase. + +Medusa provides codemods to help you update your codebase. Use these codemods when updating to their respective versions. + +*** + +## List of Medusa Codemods + + +# Replace Imports Codemod (v2.11.0+) + +In this chapter, you'll learn about the codemod that helps you replace imports in your codebase when upgrading to Medusa v2.11.0. + +## What is the Replace Imports Codemod? + +[Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) optimized the package structure by consolidating several external packages into the `@medusajs/framework` package. + +Previously, you had to install and manage packages related to MikroORM, Awilix, OpenTelemetry, and the `pg` package separately in your Medusa application. Starting with v2.11.0, these packages are included in the `@medusajs/framework` package. + +For example, instead of importing `@mikro-orm/core`, you now import it from `@medusajs/framework/mikro-orm/core`. This applies to all of the following packages: + +- `@mikro-orm/*` packages (for example, `@mikro-orm/core`, `@mikro-orm/migrations`, etc.) -> `@medusajs/framework/mikro-orm/{subpath}` +- `awilix` -> `@medusajs/framework/awilix` +- `pg` -> `@medusajs/framework/pg` +- `@opentelemetry/instrumentation-pg` -> `@medusajs/framework/opentelemetry/instrumentation-pg` +- `@opentelemetry/resources` -> `@medusajs/framework/opentelemetry/resources` +- `@opentelemetry/sdk-node` -> `@medusajs/framework/opentelemetry/sdk-node` +- `@opentelemetry/sdk-trace-node` -> `@medusajs/framework/opentelemetry/sdk-trace-node` + +To help you update your codebase to reflect these changes, Medusa provides a codemod that automatically replaces imports of these packages throughout your codebase. + +*** + +## Using the Replace Imports Codemod + +To use the replace imports codemod, create the file `replace-imports.js` in the root of your Medusa application with the following content: + +```js +#!/usr/bin/env node + +const fs = require("fs") +const path = require("path") +const { execSync } = require("child_process") + +/** + - Script to replace imports and require statements from mikro-orm/{subpath}, awilix, and pg + - to their @medusajs/framework equivalents + */ + +// Define the replacement mappings +const replacements = [ + // MikroORM imports - replace mikro-orm/{subpath} with @medusajs/framework/mikro-orm/{subpath} + { + pattern: /from\s+['"]@?mikro-orm\/([^'"]+)['"]/g, + // eslint-disable-next-line quotes + replacement: 'from "@medusajs/framework/mikro-orm/$1"', + }, + // Awilix imports - replace awilix with @medusajs/framework/awilix + { + pattern: /from\s+['"]awilix['"]/g, + // eslint-disable-next-line quotes + replacement: 'from "@medusajs/framework/awilix"', + }, + // PG imports - replace pg with @medusajs/framework/pg + { + pattern: /from\s+['"]pg['"]/g, + // eslint-disable-next-line quotes + replacement: 'from "@medusajs/framework/pg"', + }, + // OpenTelemetry imports - replace @opentelemetry/instrumentation-pg, @opentelemetry/resources, + // @opentelemetry/sdk-node, and @opentelemetry/sdk-trace-node with @medusajs/framework/opentelemetry/{subpath} + { + pattern: /from\s+['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]/g, + // eslint-disable-next-line quotes + replacement: 'from "@medusajs/framework/opentelemetry/$1"', + }, + // MikroORM require statements - replace require('@?mikro-orm/{subpath}') with require('@medusajs/framework/mikro-orm/{subpath}') + { + pattern: /require\s*\(\s*['"]@?mikro-orm\/([^'"]+)['"]\s*\)/g, + // eslint-disable-next-line quotes + replacement: 'require("@medusajs/framework/mikro-orm/$1")', + }, + // Awilix require statements - replace require('awilix') with require('@medusajs/framework/awilix') + { + pattern: /require\s*\(\s*['"]awilix['"]\s*\)/g, + // eslint-disable-next-line quotes + replacement: 'require("@medusajs/framework/awilix")', + }, + // PG require statements - replace require('pg') with require('@medusajs/framework/pg') + { + pattern: /require\s*\(\s*['"]pg['"]\s*\)/g, + // eslint-disable-next-line quotes + replacement: 'require("@medusajs/framework/pg")', + }, + // OpenTelemetry require statements - replace require('@opentelemetry/instrumentation-pg'), + // require('@opentelemetry/resources'), require('@opentelemetry/sdk-node'), and + // require('@opentelemetry/sdk-trace-node') with require('@medusajs/framework/opentelemetry/{subpath}') + { + pattern: /require\s*\(\s*['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]\s*\)/g, + // eslint-disable-next-line quotes + replacement: 'require("@medusajs/framework/opentelemetry/$1")', + }, +] + +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, "utf8") + let modifiedContent = content + let wasModified = false + + replacements.forEach(({ pattern, replacement }) => { + const newContent = modifiedContent.replace(pattern, replacement) + if (newContent !== modifiedContent) { + wasModified = true + modifiedContent = newContent + } + }) + + if (wasModified) { + fs.writeFileSync(filePath, modifiedContent) + console.log(`βœ“ Updated: ${filePath}`) + return true + } + + return false + } catch (error) { + console.error(`βœ— Error processing ${filePath}:`, error.message) + return false + } +} + +function getTargetFiles() { + try { + // Get the current script's filename to exclude it from processing + const currentScript = path.basename(__filename) + + // Find TypeScript/JavaScript files, excluding common directories that typically don't contain target imports + const findCommand = `find . -name node_modules -prune -o -name .git -prune -o -name dist -prune -o -name build -prune -o -name coverage -prune -o -name "*.ts" -print -o -name "*.js" -print -o -name "*.tsx" -print -o -name "*.jsx" -print` + const files = execSync(findCommand, { + encoding: "utf8", + maxBuffer: 50 * 1024 * 1024, // 50MB buffer + }) + .split("\n") + .filter((line) => line.trim()) + + console.log(files) + + const targetFiles = [] + let processedCount = 0 + + console.log(`πŸ“„ Scanning ${files.length} files for target imports and require statements...`) + + for (const file of files) { + try { + // Skip the current script file + const fileName = path.basename(file) + if (fileName === currentScript) { + processedCount++ + continue + } + const content = fs.readFileSync(file, "utf8") + if ( + /from\s+['"]@?mikro-orm\//.test(content) || + /from\s+['"]awilix['"]/.test(content) || + /from\s+['"]pg['"]/.test(content) || + /require\s*\(\s*['"]@?mikro-orm\//.test(content) || + /require\s*\(\s*['"]awilix['"]/.test(content) || + /require\s*\(\s*['"]pg['"]/.test(content) + ) { + targetFiles.push(file.startsWith("./") ? file.slice(2) : file) + } + processedCount++ + if (processedCount % 100 === 0) { + process.stdout.write( + `\rπŸ“„ Processed ${processedCount}/${files.length} files...` + ) + } + } catch (fileError) { + // Skip files that can't be read + continue + } + } + + if (processedCount > 0) { + console.log(`\rπŸ“„ Processed ${processedCount} files. `) + } + + return targetFiles + } catch (error) { + console.error("Error finding target files:", error.message) + return [] + } +} + +function main() { + console.log("πŸ”„ Finding files with target imports and require statements...") + + const targetFiles = getTargetFiles() + + if (targetFiles.length === 0) { + console.log("ℹ️ No files found with target imports or require statements.") + return + } + + console.log(`πŸ“ Found ${targetFiles.length} files to process`) + + let modifiedCount = 0 + let errorCount = 0 + + targetFiles.forEach((filePath) => { + const fullPath = path.resolve(filePath) + if (fs.existsSync(fullPath)) { + if (processFile(fullPath)) { + modifiedCount++ + } + } else { + console.warn(`⚠️ File not found: ${filePath}`) + errorCount++ + } + }) + + console.log("\nπŸ“Š Summary:") + console.log(` Files processed: ${targetFiles.length}`) + console.log(` Files modified: ${modifiedCount}`) + console.log(` Errors: ${errorCount}`) + + if (modifiedCount > 0) { + console.log("\nβœ… Import replacement completed successfully!") + console.log("\nπŸ’‘ Next steps:") + console.log(" 1. Review the changes with: git diff") + console.log(" 2. Run your tests to ensure everything works correctly") + console.log(" 3. Commit the changes if you're satisfied") + } else { + console.log( + "\nβœ… No modifications needed - all imports are already correct!" + ) + } +} + +// Run if called directly +if (require.main === module) { + main() +} + +module.exports = { processFile, getTargetFiles, main } +``` + +This script scans your project for files that import from `mikro-orm/{subpath}`, `awilix`, or `pg`, and replaces those imports with their new equivalents from `@medusajs/framework`. It handles both ES module `import` statements and CommonJS `require` statements in JavaScript and TypeScript files. + +Next, run the following command in your terminal to make the script executable: + +You can run the script using `node` without changing permissions. + +```bash +chmod +x replace-imports.js +``` + +Finally, execute the script with the following command: + +```bash +node replace-imports.js +``` + +This will scan your project files, apply the necessary import replacements, and provide a summary of the changes made. + +*** + +## Next Steps + +After running the codemod, review the changes made to your codebase. You can use `git diff` to see the modifications. Additionally, run your tests to ensure everything works as expected. + +If everything is working correctly, you can remove the `replace-imports.js` file from your project. You can also remove the following packages from your `package.json`, as they're now included in the `@medusajs/framework` package: + +- `@mikro-orm/*` packages (for example, `@mikro-orm/core`, `@mikro-orm/migrations`, etc.) +- `awilix` +- `pg` +- `@opentelemetry/instrumentation-pg` +- `@opentelemetry/resources` +- `@opentelemetry/sdk-node` +- `@opentelemetry/sdk-trace-node` + + # Medusa Application Configuration In this chapter, you'll learn available configurations in the Medusa application. You can change the application's configurations to customize the behavior of the application, its integrated modules and plugins, and more. @@ -4270,13 +4558,9 @@ Medusa uses [OpenTelemetry](https://opentelemetry.io/) for instrumentation and r ### Install Dependencies -Start by installing the following OpenTelemetry dependencies in your Medusa project: +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), OpenTelemetry dependencies are installed by default in new Medusa projects. If you're using an older version of Medusa, you need to install the `@opentelemetry/sdk-node`, `@opentelemetry/resources`, `@opentelemetry/sdk-trace-node`, and `@opentelemetry/instrumentation-pg` dependencies. -```bash npm2yarn -npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg -``` - -Also, install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies: +Before you start, you must install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies: ```bash npm2yarn npm install @opentelemetry/exporter-zipkin @@ -6080,8 +6364,10 @@ if (process.env.TEST_TYPE === "integration:http") { Next, create the `integration-tests/setup.js` file with the following content: +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the require statement to `@mikro-orm/core`. + ```js title="integration-tests/setup.js" -const { MetadataStorage } = require("@mikro-orm/core") +const { MetadataStorage } = require("@medusajs/framework/@mikro-orm/core") MetadataStorage.clear() ``` @@ -6652,9 +6938,44 @@ For example, the `VITE_MY_API_KEY` environment variable in the example above wil ## Environment Variables in Plugins -As explained in the [previous section](#environment-variables-in-production), environment variables are inlined into the build. This presents a limitation for plugins, where you can't use environment variables. +Environment variable support in plugins is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Refer to the [Medusa versions prior to v2.11.0](#for-medusa-versions-prior-to-v2110) section for more details if you're using an earlier version. -Instead, only the following global variable is available in plugins: +For plugins, you can use environment variables without a prefix. Then, Medusa applications that use the plugin can set the environment variable with the `PLUGIN_` prefix. + +For example, you can create a widget in your plugin that uses the `MY_API_KEY` environment variable: + +```tsx highlights={[["8"]]} +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Container, Heading } from "@medusajs/ui" + +const ProductWidget = () => { + return ( + +
+ API Key: {import.meta.env.MY_API_KEY} +
+
+ ) +} + +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) + +export default ProductWidget +``` + +Then, in the Medusa application that uses the plugin, set the environment variable with the `PLUGIN_` prefix: + +```bash +PLUGIN_MY_API_KEY=sk_123 +``` + +The `MY_API_KEY` environment variable in the plugin will be replaced with the value of `PLUGIN_MY_API_KEY` during the build process of the Medusa application. + +### Global Variables in Plugins + +Plugins also have the following global variables available: - `__BACKEND_URL__`: The URL of the Medusa backend, as set in the [Medusa configurations](https://docs.medusajs.com/learn/configurations/medusa-config#backendurl/index.html.md). - `__BASE__`: The base path of the Medusa Admin. (For example, `/app`). @@ -6693,6 +7014,14 @@ declare const __BASE__: string declare const __STOREFRONT_URL__: string ``` +### For Medusa versions prior to v2.11.0 + +### Instructions for Medusa versions prior to v2.11.0 + +As explained in the [Environment Variables in Production section](#environment-variables-in-production), environment variables are inlined into the build. This presents a limitation for plugins, where you can't use environment variables. + +Instead, you can use the [Plugin Global Variables](#global-variables-in-plugins) described above to access the backend URL, base path, and storefront URL. + # Admin Development @@ -10779,8 +11108,10 @@ npx medusa db:generate blog 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: +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`. + ```ts -import { Migration } from "@mikro-orm/migrations" +import { Migration } from "@medusajs/framework/@mikro-orm/migrations" export class Migration20241121103722 extends Migration { @@ -11548,8 +11879,10 @@ You can also write migrations manually. To do that, create a file in the `migrat For example: +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`. + ```ts title="src/modules/blog/migrations/Migration202507021059_create_author.ts" -import { Migration } from "@mikro-orm/migrations" +import { Migration } from "@medusajs/framework/@mikro-orm/migrations" export class Migration202507021059 extends Migration { @@ -11564,7 +11897,7 @@ export class Migration202507021059 extends Migration { } ``` -The migration class in the file extends the `Migration` class imported from `@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax. +The migration class in the file extends the `Migration` class imported from `@medusajs/framework/@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax. In the example above, the `up` method creates the table `author`, and the `down` method drops the table if the migration is reverted. @@ -15881,6 +16214,8 @@ So, to run database queries in a service: For example, in your service, add the following methods: +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + ```ts highlights={methodsHighlight} // other imports... import { @@ -15888,7 +16223,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -15915,7 +16250,7 @@ class BlogModuleService { You add two methods `getCount` and `getCountSql` that have the `InjectManager` decorator. Each of the methods also accept the `sharedContext` parameter which has the `MedusaContext` decorator. -The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the @mikro-orm/knex package](https://mikro-orm.io/api/knex/class/EntityManager). +The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the `@medusajs/framework/@mikro-orm/knex` package](https://mikro-orm.io/api/knex/class/EntityManager). You use the manager in the `getCount` method to retrieve the number of records in a table, and in the `getCountSql` to run a PostgreSQL query that retrieves the count. @@ -15952,7 +16287,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -16009,7 +16344,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -16052,7 +16387,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -16185,7 +16520,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -16300,7 +16635,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -16366,7 +16701,7 @@ The second parameter of the `baseRepository_.transaction` method is an object of ```ts highlights={[["24"]]} // other imports... -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import { InjectTransactionManager, MedusaContext, @@ -16409,8 +16744,8 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" -import { IsolationLevel } from "@mikro-orm/core" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" +import { IsolationLevel } from "@medusajs/framework/@mikro-orm/core" class BlogModuleService { // ... @@ -16444,7 +16779,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -16883,9 +17218,11 @@ Consider your have a MongoDB module that allows you to perform operations on a M To connect to the database, you create the following loader in your module: +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), Awilix dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `awilix`. + ```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights} import { LoaderOptions } from "@medusajs/framework/types" -import { asValue } from "awilix" +import { asValue } from "@medusajs/framework/awilix" import { MongoClient } from "mongodb" type ModuleOptions = { @@ -16951,7 +17288,7 @@ In the loader, you check first that these options are set before proceeding. The 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. +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 [`@medusajs/framework/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 @@ -17503,8 +17840,10 @@ npx medusa db:generate blog 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: +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`. + ```ts -import { Migration } from "@mikro-orm/migrations" +import { Migration } from "@medusajs/framework/@mikro-orm/migrations" export class Migration20241121103722 extends Migration { @@ -22248,17 +22587,10 @@ A basic v2 project has the following dependencies in `package.json`: "@medusajs/admin-sdk": "2.8.2", "@medusajs/cli": "2.8.2", "@medusajs/framework": "2.8.2", - "@medusajs/medusa": "2.8.2", - "@mikro-orm/core": "6.4.3", - "@mikro-orm/knex": "6.4.3", - "@mikro-orm/migrations": "6.4.3", - "@mikro-orm/postgresql": "6.4.3", - "awilix": "^8.0.1", - "pg": "^8.13.0" + "@medusajs/medusa": "2.8.2" }, "devDependencies": { "@medusajs/test-utils": "2.8.2", - "@mikro-orm/cli": "6.4.3", "@swc/core": "1.5.7", "@swc/jest": "^0.2.36", "@types/jest": "^29.5.13", @@ -22285,25 +22617,15 @@ The main changes are: - `@medusajs/framework` - `@medusajs/medusa` - `@medusajs/test-utils` (as a dev dependency) -- You need to install the following extra packages: - - Database packages: - - `@mikro-orm/core@6.4.3` - - `@mikro-orm/knex@6.4.3` - - `@mikro-orm/migrations@6.4.3` - - `@mikro-orm/postgresql@6.4.3` - - `@mikro-orm/cli@6.4.3` (as a dev dependency) - - `pg^8.13.0` - - Framework packages: - - `awilix@^8.0.1` - - Development and Testing packages: - - `@swc/core@1.5.7` - - `@swc/jest@^0.2.36` - - `@types/node@^20.0.0` - - `jest@^29.7.0` - - `ts-node@^10.9.2` - - `typescript@^5.6.2` - - `vite@^5.2.11` - - `yalc@^1.0.0-pre.53` +- You need to install the following extra packages for development and testing: + - `@swc/core@1.5.7` + - `@swc/jest@^0.2.36` + - `@types/node@^20.0.0` + - `jest@^29.7.0` + - `ts-node@^10.9.2` + - `typescript@^5.6.2` + - `vite@^5.2.11` + - `yalc@^1.0.0-pre.53` - Other packages, such as `@types/react` and `@types/react-dom`, are necessary for admin development and TypeScript support. Notice that Medusa now uses MikroORM instead of TypeORM for database functionalities. @@ -23629,7 +23951,14 @@ In this chapter, you'll learn about the different modes of running a Medusa inst ## What is Worker Mode? -By default, the Medusa application runs both the server, which handles all incoming requests, and the worker, which processes background tasks, in a single process. While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive. +By default, the Medusa application runs in `shared` mode, which runs: + +- `server`: the application server that handles incoming requests to the application's API routes. +- `worker`: the worker that processes background tasks. This includes scheduled jobs and subscribers. + +While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive. + +### Worker Mode in Production In a production environment, you should deploy two separate instances of your Medusa application: @@ -23714,6 +24043,26 @@ ADMIN_DISABLED=false ADMIN_DISABLED=true ``` +*** + +## Dividing Resources in Cluster Mode + +The `--servers` and `--workers` options were introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +When running Medusa in [cluster mode](https://docs.medusajs.com/resources/medusa-cli/commands/start#starting-medusa-in-cluster-mode/index.html.md), you can specify the number or percentage of instances that are servers or workers by passing the `--servers` and `--workers` options: + +```bash +npx medusa start --cluster 4 --servers 25% --workers 75% # Use 4 CPU cores, with 25% as servers and 75% as workers +npx medusa start --cluster 4 --servers 1 --workers 3 # Use 4 CPU cores, with 1 as server and 3 as workers +npx medusa start --cluster 4 --servers 1 --workers 1 # Use 4 CPU cores, with 1 as server and 1 as worker (the remaining 2 will run in shared mode) +``` + +In the above snippet you can see the following examples: + +- In the first example, 25% of the instances (1 out of 4) will run as servers, and 75% (3 out of 4) will run as workers. +- In the second example, 1 instance will run as a server, and 3 instances will run as workers. +- In the third example, 1 instance will run as a server, and 1 instance will run as a worker. The remaining 2 instances will run in shared mode + # Translate Medusa Admin @@ -36070,34 +36419,75 @@ Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/pro ## What is a Campaign? -A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) combines [promotions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts#what-is-a-promotion/index.html.md) under the same conditions, such as start and end dates. +A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) groups [promotions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts#what-is-a-promotion/index.html.md) under the same conditions, such as start and end dates. -Campaigns are useful for grouping promotions that share the same time frame or target audience. They're also useful for limiting the usage of promotions. +Use campaigns to group promotions that share the same time frame or target audience, and to limit promotion usage. ![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 +## Limit Promotion Usage with Campaign Budgets -Each campaign can have 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. +Each campaign can have 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 a promotion can be used. -There are two types of budgets: +There are three types of budgets: two that are global and one that is based on cart attributes. -- `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. +### Global Budgets + +A global budget limits promotion usage without considering any cart attributes. + +There are two types of global budgets: + +- `spend`: An amount that, when exceeded, makes the promotion unusable. + - For example, if the amount limit is `$100` and the total usage of this promotion exceeds that threshold, the promotion can no longer be applied. +- `usage`: The number of times a promotion can be used. + - For example, if the usage limit is `10`, customers can use the promotion only 10 times. 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) -### How Budgets Limit Promotion Usage +Global budgets track usage and limits through the following properties of the `CampaignBudget` data model: -When a customer tries to use a promotion, Medusa checks whether the campaign has a budget and if the budget limit has been reached. If so, the promotion cannot be applied. +- `limit`: The maximum amount or number of uses allowed for the promotion. +- `used`: The current amount spent or number of times the promotion has been used. -For example, if a campaign has a budget of type `usage` with a limit of `10`, and the promotion has already been used 10 times, it cannot be applied anymore and is considered expired. +### Attribute-based Budgets -However, once a promotion is applied to a cart, it remains valid until the order is completed, even if the budget limit is reached in the meantime. This ensures that customers who have already applied the promotion can still benefit from it during checkout. +Attribute-based budgets were introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + +An attribute-based budget limits promotion usage based on a cart attribute. Use these budget types to have granular control over how many times a promotion can be used based on specific attributes. + +There's one type of attribute-based budget, which is `use_by_attribute`. It allows you to limit the number of times a promotion can be used based on a specific cart attribute. + +#### Allowed Attributes + +There are two attributes that you can limit promotion usage by: + +- `customer_id`: Limits promotion usage based on the unique identifier of a customer. +- `customer_email`: Limits promotion usage based on the email address of a customer. + +These attributes are compared against the cart's `customer_id` or `email` to determine how many times the promotion has been used for that specific attribute value, and whether the budget limit has been reached. + +#### Tracking Attribute-based Usage + +The `CampaignBudgetUsage` data model tracks the usage of attribute-based budgets. It tracks how many times a promotion has been used for each unique attribute value. It includes the following properties: + +1. `attribute_value`: The value of the attribute, such as a specific customer ID or email. +2. `used`: The number of times the promotion has been used for that attribute value. + +For example, if the attribute is `customer_id`, a new `CampaignBudgetUsage` record is created for each customer that uses the promotion to track their individual usage. Once a customer exceeds the limit set in the `CampaignBudget`, they can no longer use the promotion. + +![A diagram showcasing the relation between the CampaignBudget and CampaignBudgetUsage data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1760340527/Medusa%20Resources/campaign-budget-attr_fv0v2u.jpg) + +*** + +## How Campaign Budgets Limit Promotion Usage + +When a customer tries to use a promotion, Medusa checks whether the campaign has a budget and if the budget limit has been reached. If the limit is reached, the promotion cannot be applied. + +For example, if a campaign has a `usage` budget with a limit of `10` and the promotion has already been used 10 times, it can no longer be applied and is considered expired. + +However, once a promotion is applied to a cart, it remains valid until the order is completed, even if the budget limit is reached in the meantime. This ensures that customers who already applied the promotion can still benefit from it during checkout. # Promotion Concepts @@ -44653,7 +45043,46 @@ npx medusa start |---|---|---|---|---| |\`-H \\`|Set host of the Medusa server.|\`localhost\`| |\`-p \\`|Set port of the Medusa server.|\`9000\`| -|\`--cluster \\`|Start Medusa's Node.js server in |Cluster mode is disabled by default. If the option is passed but no number is passed, Medusa will try to consume all available CPU cores.| +|\`--cluster \ \[--workers \] \[--servers \]\`|Start Medusa in cluster mode. Learn more in the |Cluster mode is disabled by default. If the option is passed but no number or percentage is passed, Medusa will try to consume all available CPU cores.| + +*** + +## Starting Medusa in Cluster Mode + +Prior to [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), the `--cluster` option accepted a number value only. You can now pass either a number or a percentage value, and you can also specify the number of servers and workers. + +Medusa supports starting the Node.js server in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster), which significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one. + +Cluster mode is disabled by default. To enable it, pass the `--cluster` option when starting Medusa: + +```bash +npx medusa start --cluster +``` + +When the `--cluster` option is passed without a number or percentage value, Medusa will try to consume all available CPU cores. + +### Specify Number or Percentage of CPU Cores + +You can specify the number or percentage of CPU cores to be used by passing a number or percentage value to the `--cluster` option: + +```bash +npx medusa start --cluster 2 # Use 2 CPU cores +npx medusa start --cluster 50% # Use 50% of available CPU +``` + +### Specify Number of Servers and Workers + +When running Medusa in cluster mode, you can specify the number or percentage of instances that are [servers or workers](https://docs.medusajs.com/docs/learn/production/worker-mode/index.html.md) by passing the `--servers` and `--workers` options: + +```bash +npx medusa start --cluster 4 --servers 25% --workers 75% # Use 4 CPU cores, with 25% as servers and 75% as workers +npx medusa start --cluster 4 --servers 1 --workers 3 # Use 4 CPU cores, with 1 as server and 3 as workers +npx medusa start --cluster 4 --servers 1 --workers 1 # Use 4 CPU cores, with 1 as server and 1 as worker (the remaining 2 will run in shared mode) +``` + +When the number or percentage of servers and workers don't add up to the total number of instances in cluster mode, the remaining instances will run in shared mode. + +Learn more in the [Worker Mode](https://docs.medusajs.com/docs/learn/production/worker-mode/index.html.md) guide. # telemetry Command - Medusa CLI Reference @@ -45020,7 +45449,46 @@ npx medusa start |---|---|---|---|---| |\`-H \\`|Set host of the Medusa server.|\`localhost\`| |\`-p \\`|Set port of the Medusa server.|\`9000\`| -|\`--cluster \\`|Start Medusa's Node.js server in |Cluster mode is disabled by default. If the option is passed but no number is passed, Medusa will try to consume all available CPU cores.| +|\`--cluster \ \[--workers \] \[--servers \]\`|Start Medusa in cluster mode. Learn more in the |Cluster mode is disabled by default. If the option is passed but no number or percentage is passed, Medusa will try to consume all available CPU cores.| + +*** + +## Starting Medusa in Cluster Mode + +Prior to [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), the `--cluster` option accepted a number value only. You can now pass either a number or a percentage value, and you can also specify the number of servers and workers. + +Medusa supports starting the Node.js server in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster), which significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one. + +Cluster mode is disabled by default. To enable it, pass the `--cluster` option when starting Medusa: + +```bash +npx medusa start --cluster +``` + +When the `--cluster` option is passed without a number or percentage value, Medusa will try to consume all available CPU cores. + +### Specify Number or Percentage of CPU Cores + +You can specify the number or percentage of CPU cores to be used by passing a number or percentage value to the `--cluster` option: + +```bash +npx medusa start --cluster 2 # Use 2 CPU cores +npx medusa start --cluster 50% # Use 50% of available CPU +``` + +### Specify Number of Servers and Workers + +When running Medusa in cluster mode, you can specify the number or percentage of instances that are [servers or workers](https://docs.medusajs.com/docs/learn/production/worker-mode/index.html.md) by passing the `--servers` and `--workers` options: + +```bash +npx medusa start --cluster 4 --servers 25% --workers 75% # Use 4 CPU cores, with 25% as servers and 75% as workers +npx medusa start --cluster 4 --servers 1 --workers 3 # Use 4 CPU cores, with 1 as server and 3 as workers +npx medusa start --cluster 4 --servers 1 --workers 1 # Use 4 CPU cores, with 1 as server and 1 as worker (the remaining 2 will run in shared mode) +``` + +When the number or percentage of servers and workers don't add up to the total number of instances in cluster mode, the remaining instances will run in shared mode. + +Learn more in the [Worker Mode](https://docs.medusajs.com/docs/learn/production/worker-mode/index.html.md) guide. # telemetry Command - Medusa CLI Reference @@ -52113,7 +52581,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -53418,7 +53886,7 @@ To register a resource in the Module's container using a loader, use the `contai import { LoaderOptions, } from "@medusajs/framework/types" -import { asValue } from "awilix" +import { asValue } from "@medusajs/framework/awilix" export default async function helloWorldLoader({ container, @@ -76325,7 +76793,7 @@ In `src/modules/product-review/service.ts`, add the following methods to the `Pr import { InjectManager, MedusaService, MedusaContext } from "@medusajs/framework/utils" import Review from "./models/review" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class ProductReviewModuleService extends MedusaService({ Review, @@ -79587,7 +80055,7 @@ Loaders are created in a TypeScript or JavaScript file under the `loaders` direc ```ts title="src/modules/contentful/loader/create-content-models.ts" highlights={loaderHighlights} import { LoaderOptions } from "@medusajs/framework/types" -import { asValue } from "awilix" +import { asValue } from "@medusajs/framework/awilix" import { createClient } from "contentful-management" import { MedusaError } from "@medusajs/framework/utils" @@ -92854,15 +93322,7 @@ Refer to the [Instrumentation](https://docs.medusajs.com/docs/learn/debugging-an ### a. Install Instrumentation Dependencies -To set up instrumentation in Medusa, you need to install the necessary OpenTelemetry dependencies. - -In your Medusa application's directory, run the following command: - -```bash npm2yarn -npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg -``` - -Then, you need to install the dependencies necessary for the monitoring tool you want to use, which is Sentry in this case. +To set up instrumentation in Medusa, you need to install the dependencies necessary for the monitoring tool you want to use, which is Sentry in this case. So, run the following command to install the necessary Sentry dependencies: @@ -96773,7 +97233,7 @@ In `src/modules/wishlist/service.ts`, add the following imports and method: // other imports... import { InjectManager } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" export default class WishlistModuleService extends MedusaService({ Wishlist, @@ -115723,7 +116183,7 @@ Before adding the step that does this, you'll add a method in the `RestockModule // other imports... import { InjectManager, MedusaContext } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class RestockModuleService extends MedusaService({ RestockSubscription, diff --git a/www/apps/book/sidebar.mjs b/www/apps/book/sidebar.mjs index adb173e58f..a485a42451 100644 --- a/www/apps/book/sidebar.mjs +++ b/www/apps/book/sidebar.mjs @@ -679,7 +679,7 @@ export const sidebars = [ { type: "link", path: "/learn/production/worker-mode", - title: "Worker Mode", + title: "Worker Modes", }, { type: "link", @@ -709,6 +709,18 @@ export const sidebars = [ path: "https://github.com/medusajs/medusa/releases", title: "Release Notes", }, + { + type: "link", + path: "/learn/codemods", + title: "Codemods", + children: [ + { + type: "link", + title: "Replace Imports (v2.11.0+)", + path: "/learn/codemods/replace-imports", + }, + ], + }, ], }, { diff --git a/www/apps/resources/app/commerce-modules/promotion/application-method/page.mdx b/www/apps/resources/app/commerce-modules/promotion/application-method/page.mdx index bdc92dcdc1..c4066a69a1 100644 --- a/www/apps/resources/app/commerce-modules/promotion/application-method/page.mdx +++ b/www/apps/resources/app/commerce-modules/promotion/application-method/page.mdx @@ -75,12 +75,12 @@ The [ApplicationMethod data model](/references/promotion/models/ApplicationMetho - Is the discounted amount applied to each item or split between the applicable items? + Is the discounted amount applied to each item, split between the applicable items, or applied on specific number of items? - `each`, `across` + `each`, `across`, `once` @@ -113,7 +113,11 @@ In this example, the cart must have two product variants with the SKU `SHIRT` fo ## Maximum Quantity Restriction -When the `allocation` property in the `ApplicationMethod` is set to `each`, you can set the `max_quantity` property of `ApplicationMethod` to limit how many item quantities the promotion is applied to. +You can restrict how many items the promotion is applied to either at the item level or the cart level. + +### Item Level Restriction + +When the `allocation` property in the `ApplicationMethod` is set to `each`, you can set the `max_quantity` property of `ApplicationMethod` to limit how many quantities of each applicable item the promotion is applied to. For example, if the `max_quantity` property is set to `1` and the customer has a line item with quantity two in the cart, the promotion is only applied to one of them. @@ -147,4 +151,112 @@ This condition is applied on the quantity of every applicable item in the cart. ] } } -``` \ No newline at end of file +``` + +### Cart Level Restriction + + + +The `once` allocation type is available from [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + + + +When the `allocation` property in the `ApplicationMethod` is set to `once`, you must set the `max_quantity` property of `ApplicationMethod`. It limits how many items in total the promotion is applied to. + +In this scenario, the Promotion Module prioritizes which applicable items the promotion is applied to based on the following rules: + +1. Prioritize items with the lowest price. +2. Distribute the promotion sequentially until the `max_quantity` is reached. + +#### Example 1 + +Consider: + +- A promotion whose application method has its `allocation` property set to `once` and `max_quantity` set to `2`. +- A cart with three items having different prices, each with a quantity of `1`. + +The Promotion Module will apply the promotion to the two items with the lowest price. + +```json title="Example Cart" +{ + "cart": { + "items": [ + { + "id": "item_1", + "price": 10, + "quantity": 1 // The promotion is applied to this item + }, + { + "id": "item_2", + "price": 20, + "quantity": 1 // The promotion is applied to this item + }, + { + "id": "item_3", + "price": 30, + "quantity": 1 // The promotion is NOT applied to this item + } + ] + } +} +``` + +#### Example 2 + +Consider: + +- A promotion whose application method has its `allocation` property set to `once` and `max_quantity` set to `2`. +- A cart with two items having different prices and quantities greater than `2`. + +The Promotion Module will try to apply the promotion to the item with the lowest price first: + +```json title="Example Cart" +{ + "cart": { + "items": [ + { + "id": "item_1", + "price": 10, + "quantity": 3 // The promotion is applied to 2 of this item + }, + { + "id": "item_2", + "price": 20, + "quantity": 4 // The promotion is NOT applied to this item + } + ] + } +} +``` + +Since that item has a quantity of `3`, the promotion is applied to `2` of that item, reaching the `max_quantity` limit. The promotion is not applied to the other item. + +#### Example 3 + +Consider: + +- A promotion whose application method has its `allocation` property set to `once` and `max_quantity` set to `5`. +- A cart with two items having different prices and quantities less than `5`. + +The Promotion Module will try to apply the promotion to the item with the lowest price first: + +```json title="Example Cart" +{ + "cart": { + "items": [ + { + "id": "item_1", + "price": 10, + "quantity": 3 // The promotion is applied to all 3 of this item + }, + { + "id": "item_2", + "price": 20, + "quantity": 4 // The promotion is applied to 2 of this item + } + ] + } +} +``` + +The promotion is applied to all `3` quantities of the item with the lowest price. Since the `max_quantity` is `5`, the promotion is applied to `2` quantities of the other item, reaching the `max_quantity` limit. \ No newline at end of file diff --git a/www/apps/resources/app/commerce-modules/promotion/campaign/page.mdx b/www/apps/resources/app/commerce-modules/promotion/campaign/page.mdx index d2962d5069..862a818e81 100644 --- a/www/apps/resources/app/commerce-modules/promotion/campaign/page.mdx +++ b/www/apps/resources/app/commerce-modules/promotion/campaign/page.mdx @@ -14,31 +14,76 @@ Refer to this [Medusa Admin User Guide](!user-guide!/promotions/campaigns) to le ## What is a Campaign? -A [Campaign](/references/promotion/models/Campaign) combines [promotions](../concepts/page.mdx#what-is-a-promotion) under the same conditions, such as start and end dates. +A [Campaign](/references/promotion/models/Campaign) groups [promotions](../concepts/page.mdx#what-is-a-promotion) under the same conditions, such as start and end dates. -Campaigns are useful for grouping promotions that share the same time frame or target audience. They're also useful for limiting the usage of promotions. +Use campaigns to group promotions that share the same time frame or target audience, and to limit promotion usage. ![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 +## Limit Promotion Usage with Campaign Budgets -Each campaign can have a budget represented by the [CampaignBudget data model](/references/promotion/models/CampaignBudget). The budget limits how many times the promotion can be used. +Each campaign can have a budget represented by the [CampaignBudget data model](/references/promotion/models/CampaignBudget). The budget limits how many times a promotion can be used. -There are two types of budgets: +There are three types of budgets: two that are global and one that is based on cart attributes. -- `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. +### Global Budgets + +A global budget limits promotion usage without considering any cart attributes. + +There are two types of global budgets: + +- `spend`: An amount that, when exceeded, makes the promotion unusable. + - For example, if the amount limit is `$100` and the total usage of this promotion exceeds that threshold, the promotion can no longer be applied. +- `usage`: The number of times a promotion can be used. + - For example, if the usage limit is `10`, customers can use the promotion only 10 times. 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) -### How Budgets Limit Promotion Usage +Global budgets track usage and limits through the following properties of the `CampaignBudget` data model: -When a customer tries to use a promotion, Medusa checks whether the campaign has a budget and if the budget limit has been reached. If so, the promotion cannot be applied. +- `limit`: The maximum amount or number of uses allowed for the promotion. +- `used`: The current amount spent or number of times the promotion has been used. -For example, if a campaign has a budget of type `usage` with a limit of `10`, and the promotion has already been used 10 times, it cannot be applied anymore and is considered expired. +### Attribute-based Budgets -However, once a promotion is applied to a cart, it remains valid until the order is completed, even if the budget limit is reached in the meantime. This ensures that customers who have already applied the promotion can still benefit from it during checkout. \ No newline at end of file + + +Attribute-based budgets were introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). + + + +An attribute-based budget limits promotion usage based on a cart attribute. Use these budget types to have granular control over how many times a promotion can be used based on specific attributes. + +There's one type of attribute-based budget, which is `use_by_attribute`. It allows you to limit the number of times a promotion can be used based on a specific cart attribute. + +#### Allowed Attributes + +There are two attributes that you can limit promotion usage by: + +- `customer_id`: Limits promotion usage based on the unique identifier of a customer. +- `customer_email`: Limits promotion usage based on the email address of a customer. + +These attributes are compared against the cart's `customer_id` or `email` to determine how many times the promotion has been used for that specific attribute value, and whether the budget limit has been reached. + +#### Tracking Attribute-based Usage + +The `CampaignBudgetUsage` data model tracks the usage of attribute-based budgets. It tracks how many times a promotion has been used for each unique attribute value. It includes the following properties: + +1. `attribute_value`: The value of the attribute, such as a specific customer ID or email. +2. `used`: The number of times the promotion has been used for that attribute value. + +For example, if the attribute is `customer_id`, a new `CampaignBudgetUsage` record is created for each customer that uses the promotion to track their individual usage. Once a customer exceeds the limit set in the `CampaignBudget`, they can no longer use the promotion. + +![A diagram showcasing the relation between the CampaignBudget and CampaignBudgetUsage data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1760340527/Medusa%20Resources/campaign-budget-attr_fv0v2u.jpg) + +--- + +## How Campaign Budgets Limit Promotion Usage + +When a customer tries to use a promotion, Medusa checks whether the campaign has a budget and if the budget limit has been reached. If the limit is reached, the promotion cannot be applied. + +For example, if a campaign has a `usage` budget with a limit of `10` and the promotion has already been used 10 times, it can no longer be applied and is considered expired. + +However, once a promotion is applied to a cart, it remains valid until the order is completed, even if the budget limit is reached in the meantime. This ensures that customers who already applied the promotion can still benefit from it during checkout. \ No newline at end of file diff --git a/www/apps/resources/app/data-model-repository-reference/methods/create/page.mdx b/www/apps/resources/app/data-model-repository-reference/methods/create/page.mdx index 2650ec878a..a420fd33d0 100644 --- a/www/apps/resources/app/data-model-repository-reference/methods/create/page.mdx +++ b/www/apps/resources/app/data-model-repository-reference/methods/create/page.mdx @@ -36,6 +36,12 @@ This reference assumes you've already resolved the data model repository, as exp ## Create Records + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + ```ts import { InjectTransactionManager, @@ -43,7 +49,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ diff --git a/www/apps/resources/app/data-model-repository-reference/methods/delete/page.mdx b/www/apps/resources/app/data-model-repository-reference/methods/delete/page.mdx index ef1e92888e..be166f4a5a 100644 --- a/www/apps/resources/app/data-model-repository-reference/methods/delete/page.mdx +++ b/www/apps/resources/app/data-model-repository-reference/methods/delete/page.mdx @@ -35,6 +35,12 @@ This reference assumes you've already resolved the data model repository, as exp ## Delete Record by ID + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + ```ts import { InjectTransactionManager, @@ -42,7 +48,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -92,7 +98,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ diff --git a/www/apps/resources/app/data-model-repository-reference/methods/find/page.mdx b/www/apps/resources/app/data-model-repository-reference/methods/find/page.mdx index bdacfeb651..fd34dc35b6 100644 --- a/www/apps/resources/app/data-model-repository-reference/methods/find/page.mdx +++ b/www/apps/resources/app/data-model-repository-reference/methods/find/page.mdx @@ -106,7 +106,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -149,7 +149,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -213,7 +213,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -285,7 +285,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -349,7 +349,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -403,6 +403,12 @@ The method returns an array of records. The number of records is less than or eq ## Sort Records + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + ```ts title="src/modules/blog/service.ts" import { InjectTransactionManager, @@ -410,7 +416,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ diff --git a/www/apps/resources/app/data-model-repository-reference/methods/findAndCount/page.mdx b/www/apps/resources/app/data-model-repository-reference/methods/findAndCount/page.mdx index 3cf0d14510..0a3d4bd083 100644 --- a/www/apps/resources/app/data-model-repository-reference/methods/findAndCount/page.mdx +++ b/www/apps/resources/app/data-model-repository-reference/methods/findAndCount/page.mdx @@ -99,6 +99,12 @@ This reference assumes you've already resolved the data model repository, as exp To retrieve a list of records matching a set of filters, use the `findAndCount` method of the data model repository: + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + ```ts title="src/modules/blog/service.ts" import { InjectTransactionManager, @@ -106,7 +112,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -156,7 +162,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -226,7 +232,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -306,7 +312,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -378,7 +384,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -439,7 +445,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ diff --git a/www/apps/resources/app/data-model-repository-reference/methods/restore/page.mdx b/www/apps/resources/app/data-model-repository-reference/methods/restore/page.mdx index 62be2c8e3a..c87ad9ab93 100644 --- a/www/apps/resources/app/data-model-repository-reference/methods/restore/page.mdx +++ b/www/apps/resources/app/data-model-repository-reference/methods/restore/page.mdx @@ -37,6 +37,12 @@ This reference assumes you've already resolved the data model repository, as exp ## Restore Record by ID + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + ```ts import { InjectTransactionManager, @@ -44,7 +50,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -140,7 +146,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -236,7 +242,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ diff --git a/www/apps/resources/app/data-model-repository-reference/methods/softDelete/page.mdx b/www/apps/resources/app/data-model-repository-reference/methods/softDelete/page.mdx index cf9f76d224..9780b000c8 100644 --- a/www/apps/resources/app/data-model-repository-reference/methods/softDelete/page.mdx +++ b/www/apps/resources/app/data-model-repository-reference/methods/softDelete/page.mdx @@ -39,6 +39,12 @@ This reference assumes you've already resolved the data model repository, as exp ## Soft-Delete Record by ID + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + ```ts import { InjectTransactionManager, @@ -46,7 +52,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -142,7 +148,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ @@ -238,7 +244,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ diff --git a/www/apps/resources/app/data-model-repository-reference/methods/update/page.mdx b/www/apps/resources/app/data-model-repository-reference/methods/update/page.mdx index e5d1fd2b1b..54e333f0ee 100644 --- a/www/apps/resources/app/data-model-repository-reference/methods/update/page.mdx +++ b/www/apps/resources/app/data-model-repository-reference/methods/update/page.mdx @@ -50,6 +50,12 @@ This reference assumes you've already resolved the data model repository, as exp ## Update Records + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + ```ts import { InjectTransactionManager, @@ -57,7 +63,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ diff --git a/www/apps/resources/app/data-model-repository-reference/methods/upsert/page.mdx b/www/apps/resources/app/data-model-repository-reference/methods/upsert/page.mdx index 130abaac44..2ef4814d19 100644 --- a/www/apps/resources/app/data-model-repository-reference/methods/upsert/page.mdx +++ b/www/apps/resources/app/data-model-repository-reference/methods/upsert/page.mdx @@ -35,6 +35,12 @@ This reference assumes you've already resolved the data model repository, as exp ## Upsert Records + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + ```ts import { InjectTransactionManager, @@ -42,7 +48,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ diff --git a/www/apps/resources/app/data-model-repository-reference/methods/upsertWithReplace/page.mdx b/www/apps/resources/app/data-model-repository-reference/methods/upsertWithReplace/page.mdx index d22b0c5621..7328f6fc4e 100644 --- a/www/apps/resources/app/data-model-repository-reference/methods/upsertWithReplace/page.mdx +++ b/www/apps/resources/app/data-model-repository-reference/methods/upsertWithReplace/page.mdx @@ -41,6 +41,12 @@ This reference assumes you've already resolved the data model repository, as exp ## Upsert Records + + +As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`. + + + ```ts import { InjectTransactionManager, @@ -48,7 +54,7 @@ import { MedusaService, } from "@medusajs/framework/utils" import { Context, InferTypeOf, DAL } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" import Post from "./models/post" class BlogModuleService extends MedusaService({ diff --git a/www/apps/resources/app/examples/page.mdx b/www/apps/resources/app/examples/page.mdx index a98330ca48..71f0a722dc 100644 --- a/www/apps/resources/app/examples/page.mdx +++ b/www/apps/resources/app/examples/page.mdx @@ -1646,7 +1646,7 @@ import { MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class BlogModuleService { // ... @@ -2971,7 +2971,7 @@ To register a resource in the Module's container using a loader, use the `contai import { LoaderOptions, } from "@medusajs/framework/types" -import { asValue } from "awilix" +import { asValue } from "@medusajs/framework/awilix" export default async function helloWorldLoader({ container, diff --git a/www/apps/resources/app/how-to-tutorials/tutorials/product-reviews/page.mdx b/www/apps/resources/app/how-to-tutorials/tutorials/product-reviews/page.mdx index 47b390542a..9c0ce5955a 100644 --- a/www/apps/resources/app/how-to-tutorials/tutorials/product-reviews/page.mdx +++ b/www/apps/resources/app/how-to-tutorials/tutorials/product-reviews/page.mdx @@ -1484,7 +1484,7 @@ In `src/modules/product-review/service.ts`, add the following methods to the `Pr import { InjectManager, MedusaService, MedusaContext } from "@medusajs/framework/utils" import Review from "./models/review" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class ProductReviewModuleService extends MedusaService({ Review, diff --git a/www/apps/resources/app/integrations/guides/contentful/page.mdx b/www/apps/resources/app/integrations/guides/contentful/page.mdx index bf6442f78b..afebb71038 100644 --- a/www/apps/resources/app/integrations/guides/contentful/page.mdx +++ b/www/apps/resources/app/integrations/guides/contentful/page.mdx @@ -175,7 +175,7 @@ export const loaderHighlights = [ ```ts title="src/modules/contentful/loader/create-content-models.ts" highlights={loaderHighlights} import { LoaderOptions } from "@medusajs/framework/types" -import { asValue } from "awilix" +import { asValue } from "@medusajs/framework/awilix" import { createClient } from "contentful-management" import { MedusaError } from "@medusajs/framework/utils" diff --git a/www/apps/resources/app/integrations/guides/sentry/page.mdx b/www/apps/resources/app/integrations/guides/sentry/page.mdx index b3897dd9b6..5a75071eca 100644 --- a/www/apps/resources/app/integrations/guides/sentry/page.mdx +++ b/www/apps/resources/app/integrations/guides/sentry/page.mdx @@ -94,15 +94,7 @@ Refer to the [Instrumentation](!docs!/learn/debugging-and-testing/instrumentatio ### a. Install Instrumentation Dependencies -To set up instrumentation in Medusa, you need to install the necessary OpenTelemetry dependencies. - -In your Medusa application's directory, run the following command: - -```bash npm2yarn -npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg -``` - -Then, you need to install the dependencies necessary for the monitoring tool you want to use, which is Sentry in this case. +To set up instrumentation in Medusa, you need to install the dependencies necessary for the monitoring tool you want to use, which is Sentry in this case. So, run the following command to install the necessary Sentry dependencies: diff --git a/www/apps/resources/app/medusa-cli/commands/start/page.mdx b/www/apps/resources/app/medusa-cli/commands/start/page.mdx index 66be69ea73..db6d979606 100644 --- a/www/apps/resources/app/medusa-cli/commands/start/page.mdx +++ b/www/apps/resources/app/medusa-cli/commands/start/page.mdx @@ -65,19 +65,66 @@ npx medusa start - `--cluster ` + `--cluster [--workers ] [--servers ]` - Start Medusa's Node.js server 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. + Start Medusa in cluster mode. Learn more in the [Starting Medusa in Cluster Mode](#starting-medusa-in-cluster-mode) section. - Cluster mode is disabled by default. If the option is passed but no number is passed, Medusa will try to consume all available CPU cores. + Cluster mode is disabled by default. If the option is passed but no number or percentage is passed, Medusa will try to consume all available CPU cores. - \ No newline at end of file + + +--- + +## Starting Medusa in Cluster Mode + + + +Prior to [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), the `--cluster` option accepted a number value only. You can now pass either a number or a percentage value, and you can also specify the number of servers and workers. + + + +Medusa supports starting the Node.js server in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster), which significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one. + +Cluster mode is disabled by default. To enable it, pass the `--cluster` option when starting Medusa: + +```bash +npx medusa start --cluster +``` + +When the `--cluster` option is passed without a number or percentage value, Medusa will try to consume all available CPU cores. + +### Specify Number or Percentage of CPU Cores + +You can specify the number or percentage of CPU cores to be used by passing a number or percentage value to the `--cluster` option: + +```bash +npx medusa start --cluster 2 # Use 2 CPU cores +npx medusa start --cluster 50% # Use 50% of available CPU +``` + +### Specify Number of Servers and Workers + +When running Medusa in cluster mode, you can specify the number or percentage of instances that are [servers or workers](!docs!/learn/production/worker-mode) by passing the `--servers` and `--workers` options: + +```bash +npx medusa start --cluster 4 --servers 25% --workers 75% # Use 4 CPU cores, with 25% as servers and 75% as workers +npx medusa start --cluster 4 --servers 1 --workers 3 # Use 4 CPU cores, with 1 as server and 3 as workers +npx medusa start --cluster 4 --servers 1 --workers 1 # Use 4 CPU cores, with 1 as server and 1 as worker (the remaining 2 will run in shared mode) +``` + +When the number or percentage of servers and workers don't add up to the total number of instances in cluster mode, the remaining instances will run in shared mode. + + + +Learn more in the [Worker Mode](!docs!/learn/production/worker-mode) guide. + + \ No newline at end of file diff --git a/www/apps/resources/app/plugins/guides/wishlist/page.mdx b/www/apps/resources/app/plugins/guides/wishlist/page.mdx index 84ba051d14..62a86ccfef 100644 --- a/www/apps/resources/app/plugins/guides/wishlist/page.mdx +++ b/www/apps/resources/app/plugins/guides/wishlist/page.mdx @@ -1958,7 +1958,7 @@ In `src/modules/wishlist/service.ts`, add the following imports and method: // other imports... import { InjectManager } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" export default class WishlistModuleService extends MedusaService({ Wishlist, diff --git a/www/apps/resources/app/recipes/commerce-automation/restock-notification/page.mdx b/www/apps/resources/app/recipes/commerce-automation/restock-notification/page.mdx index 9180149614..5b74a3dd79 100644 --- a/www/apps/resources/app/recipes/commerce-automation/restock-notification/page.mdx +++ b/www/apps/resources/app/recipes/commerce-automation/restock-notification/page.mdx @@ -1133,7 +1133,7 @@ Before adding the step that does this, you'll add a method in the `RestockModule // other imports... import { InjectManager, MedusaContext } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" -import { EntityManager } from "@mikro-orm/knex" +import { EntityManager } from "@medusajs/framework/@mikro-orm/knex" class RestockModuleService extends MedusaService({ RestockSubscription, diff --git a/www/apps/resources/app/storefront-development/products/inventory/page.mdx b/www/apps/resources/app/storefront-development/products/inventory/page.mdx index 490a29cbb0..4278a226be 100644 --- a/www/apps/resources/app/storefront-development/products/inventory/page.mdx +++ b/www/apps/resources/app/storefront-development/products/inventory/page.mdx @@ -61,7 +61,9 @@ In this example, you retrieve the product variants' inventory quantity by passin A variant is in stock if: 1. Its `manage_inventory`'s value is `false`, meaning that Medusa doesn't keep track of its inventory. -2. If its `inventory_quantity`'s value is greater than `0`. This property is only available on variants whose `manage_inventory` is `false`. +2. If its `inventory_quantity`'s value is greater than `0`. + - This property is only available on variants whose `manage_inventory` is `false`. + - If the variant doesn't have inventory levels in the stock location associated with the API's scope, its `inventory_quantity` will be `null`. --- diff --git a/www/apps/resources/app/troubleshooting/test-errors/page.mdx b/www/apps/resources/app/troubleshooting/test-errors/page.mdx index 51100a13eb..44558c7b28 100644 --- a/www/apps/resources/app/troubleshooting/test-errors/page.mdx +++ b/www/apps/resources/app/troubleshooting/test-errors/page.mdx @@ -24,7 +24,7 @@ module.exports = { Then, create the `integration-tests/setup.js` file with the following content: ```js title="integration-tests/setup.js" -const { MetadataStorage } = require("@mikro-orm/core") +const { MetadataStorage } = require("@medusajs/framework/@mikro-orm/core") MetadataStorage.clear() ``` diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index e7d5d132d9..5a0899debe 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -68,8 +68,8 @@ export const generatedEditDates = { "app/commerce-modules/promotion/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00", "app/commerce-modules/promotion/_events/page.mdx": "2024-07-03T19:27:13+03:00", "app/commerce-modules/promotion/actions/page.mdx": "2025-06-27T15:42:19.142Z", - "app/commerce-modules/promotion/application-method/page.mdx": "2025-10-01T09:54:44.931Z", - "app/commerce-modules/promotion/campaign/page.mdx": "2025-02-26T11:32:24.484Z", + "app/commerce-modules/promotion/application-method/page.mdx": "2025-10-14T12:09:22.188Z", + "app/commerce-modules/promotion/campaign/page.mdx": "2025-10-13T07:34:59.008Z", "app/commerce-modules/promotion/concepts/page.mdx": "2025-02-26T11:31:54.391Z", "app/commerce-modules/promotion/page.mdx": "2025-04-17T08:48:14.643Z", "app/commerce-modules/region/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00", @@ -565,7 +565,7 @@ export const generatedEditDates = { "app/medusa-cli/commands/develop/page.mdx": "2025-09-01T15:31:35.072Z", "app/medusa-cli/commands/exec/page.mdx": "2025-01-16T09:51:17.050Z", "app/medusa-cli/commands/new/page.mdx": "2024-08-28T10:43:34.110Z", - "app/medusa-cli/commands/start/page.mdx": "2025-04-08T11:56:15.522Z", + "app/medusa-cli/commands/start/page.mdx": "2025-10-13T10:27:25.345Z", "app/medusa-cli/commands/telemtry/page.mdx": "2025-01-16T09:51:24.323Z", "app/medusa-cli/commands/user/page.mdx": "2025-09-01T15:36:38.978Z", "app/recipes/marketplace/examples/restaurant-delivery/page.mdx": "2025-08-25T07:30:37.299Z", @@ -877,7 +877,7 @@ export const generatedEditDates = { "references/promotion/interfaces/promotion.IPromotionModuleService/page.mdx": "2024-11-25T17:49:58.612Z", "references/types/EventBusTypes/interfaces/types.EventBusTypes.IEventBusService/page.mdx": "2025-04-11T09:04:46.063Z", "references/types/TransactionBaseTypes/interfaces/types.TransactionBaseTypes.ITransactionBaseService/page.mdx": "2024-09-06T00:11:08.494Z", - "app/storefront-development/products/inventory/page.mdx": "2025-03-27T14:46:51.435Z", + "app/storefront-development/products/inventory/page.mdx": "2025-10-13T10:18:19.391Z", "references/auth/IAuthModuleService/methods/auth.IAuthModuleService.updateAuthIdentities/page.mdx": "2025-05-20T07:51:40.735Z", "references/auth/IAuthModuleService/methods/auth.IAuthModuleService.updateProvider/page.mdx": "2025-05-20T07:51:40.735Z", "references/auth/IAuthModuleService/methods/auth.IAuthModuleService.updateProviderIdentities/page.mdx": "2025-05-20T07:51:40.735Z", @@ -2165,7 +2165,7 @@ export const generatedEditDates = { "app/commerce-modules/sales-channel/links-to-other-modules/page.mdx": "2025-04-17T16:00:09.483Z", "app/commerce-modules/stock-location/links-to-other-modules/page.mdx": "2025-04-17T16:02:51.467Z", "app/commerce-modules/store/links-to-other-modules/page.mdx": "2025-04-17T16:03:16.419Z", - "app/examples/page.mdx": "2025-07-16T09:53:26.163Z", + "app/examples/page.mdx": "2025-09-29T15:36:18.867Z", "app/medusa-cli/commands/build/page.mdx": "2025-09-01T15:30:05.995Z", "app/js-sdk/page.mdx": "2025-08-01T14:17:07.509Z", "references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.apiKey/page.mdx": "2025-05-20T07:51:40.924Z", @@ -5564,7 +5564,7 @@ export const generatedEditDates = { "references/modules/sales_channel_models/page.mdx": "2024-12-10T14:55:13.205Z", "references/types/DmlTypes/types/types.DmlTypes.KnownDataTypes/page.mdx": "2024-12-17T16:57:19.922Z", "references/types/DmlTypes/types/types.DmlTypes.RelationshipTypes/page.mdx": "2024-12-10T14:54:55.435Z", - "app/recipes/commerce-automation/restock-notification/page.mdx": "2025-07-14T09:35:35.226Z", + "app/recipes/commerce-automation/restock-notification/page.mdx": "2025-09-29T15:35:49.320Z", "app/integrations/guides/shipstation/page.mdx": "2025-05-20T07:51:40.717Z", "app/nextjs-starter/guides/customize-stripe/page.mdx": "2025-07-15T08:50:51.997Z", "references/core_flows/Cart/Workflows_Cart/functions/core_flows.Cart.Workflows_Cart.listShippingOptionsForCartWithPricingWorkflow/page.mdx": "2025-09-18T17:04:38.644Z", @@ -5831,7 +5831,7 @@ export const generatedEditDates = { "references/core_flows/types/core_flows.ThrowUnlessPaymentCollectionNotePaidInput/page.mdx": "2025-06-25T10:11:33.516Z", "references/core_flows/types/core_flows.ValidatePaymentsRefundStepInput/page.mdx": "2025-06-25T10:11:34.185Z", "references/core_flows/types/core_flows.ValidateRefundStepInput/page.mdx": "2025-09-12T14:10:36.022Z", - "app/plugins/guides/wishlist/page.mdx": "2025-08-15T10:52:37.465Z", + "app/plugins/guides/wishlist/page.mdx": "2025-09-29T15:35:46.093Z", "app/plugins/page.mdx": "2025-02-26T11:39:25.709Z", "app/admin-components/components/data-table/page.mdx": "2025-03-03T14:55:58.556Z", "references/order_models/variables/order_models.Order/page.mdx": "2025-09-18T17:05:02.047Z", @@ -5870,7 +5870,7 @@ export const generatedEditDates = { "references/types/interfaces/types.BaseProductTypeListParams/page.mdx": "2025-01-27T11:43:54.550Z", "references/core_flows/Order/Steps_Order/variables/core_flows.Order.Steps_Order.updateOrderChangesStepId/page.mdx": "2025-01-27T11:43:49.278Z", "app/commerce-modules/payment/account-holder/page.mdx": "2025-04-07T07:31:20.235Z", - "app/troubleshooting/test-errors/page.mdx": "2025-01-31T13:08:42.639Z", + "app/troubleshooting/test-errors/page.mdx": "2025-09-29T15:35:52.805Z", "app/commerce-modules/product/variant-inventory/page.mdx": "2025-04-25T13:25:02.408Z", "app/examples/guides/custom-item-price/page.mdx": "2025-06-26T11:53:06.748Z", "references/core_flows/Cart/Steps_Cart/functions/core_flows.Cart.Steps_Cart.validateShippingStep/page.mdx": "2025-04-11T09:04:35.729Z", @@ -6542,7 +6542,7 @@ export const generatedEditDates = { "references/utils/types/utils.NormalizedRow/page.mdx": "2025-06-05T19:05:53.365Z", "references/utils/utils.Payment/page.mdx": "2025-06-05T19:05:53.489Z", "app/integrations/guides/slack/page.mdx": "2025-06-26T12:57:20.880Z", - "app/integrations/guides/sentry/page.mdx": "2025-06-16T10:11:29.955Z", + "app/integrations/guides/sentry/page.mdx": "2025-10-02T10:30:51.194Z", "app/integrations/guides/mailchimp/page.mdx": "2025-06-26T11:59:15.303Z", "app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx": "2025-06-26T11:55:27.175Z", "references/types/CommonTypes/interfaces/types.CommonTypes.CookieOptions/page.mdx": "2025-06-25T10:11:37.088Z", @@ -6611,6 +6611,15 @@ export const generatedEditDates = { "app/integrations/guides/meilisearch/page.mdx": "2025-10-09T11:30:25.084Z", "app/nextjs-starter/guides/storefront-returns/page.mdx": "2025-09-22T06:02:00.580Z", "references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.views/page.mdx": "2025-09-18T17:04:59.240Z", + "app/data-model-repository-reference/methods/create/page.mdx": "2025-10-09T11:42:23.826Z", + "app/data-model-repository-reference/methods/delete/page.mdx": "2025-10-09T11:42:59.141Z", + "app/data-model-repository-reference/methods/find/page.mdx": "2025-10-09T11:43:05.478Z", + "app/data-model-repository-reference/methods/findAndCount/page.mdx": "2025-10-09T11:43:49.941Z", + "app/data-model-repository-reference/methods/restore/page.mdx": "2025-10-09T11:44:02.906Z", + "app/data-model-repository-reference/methods/softDelete/page.mdx": "2025-10-09T11:44:16.158Z", + "app/data-model-repository-reference/methods/upsert/page.mdx": "2025-10-09T11:44:39.838Z", + "app/data-model-repository-reference/methods/update/page.mdx": "2025-10-09T11:44:27.403Z", + "app/data-model-repository-reference/methods/upsertWithReplace/page.mdx": "2025-10-09T11:44:53.535Z", "app/how-to-tutorials/tutorials/agentic-commerce/page.mdx": "2025-10-09T11:25:48.831Z", "app/storefront-development/production-optimizations/page.mdx": "2025-10-03T13:28:37.909Z", "app/troubleshooting/subscribers/not-working/page.mdx": "2025-10-16T09:25:57.376Z" diff --git a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs index f186fac949..43f5cc9dbc 100644 --- a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs +++ b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs @@ -6419,6 +6419,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = { "path": "https://docs.medusajs.com/user-guide/orders/returns", "children": [] }, + { + "loaded": true, + "isPathHref": true, + "type": "ref", + "title": "Manage Refund Reasons", + "path": "https://docs.medusajs.com/user-guide/settings/refund-reasons", + "children": [] + }, { "loaded": true, "isPathHref": true, diff --git a/www/apps/user-guide/app/orders/payments/page.mdx b/www/apps/user-guide/app/orders/payments/page.mdx index 2fa66ab570..18cae14486 100644 --- a/www/apps/user-guide/app/orders/payments/page.mdx +++ b/www/apps/user-guide/app/orders/payments/page.mdx @@ -93,7 +93,13 @@ To capture an order’s payment: ## Refund Payment -If you've made changes to an order, such as return or exchange items, that resulted in a negative outstanding amount, you can refund a previously-captured payment. +You can refund a payment either partially or in full. This can be done for various reasons, such as when a customer returns an item or if there was an error in the order. + + + +Prior to [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), you were only able to refund a payment after creating a return or making an order change. From Medusa v2.11.0 onwards, you can refund a payment directly from the order details page without needing to create a return or make an order change. If you're unable to refund a payment, contact your technical team to [update your Medusa application](!docs!/learn/update). + + Refunding the payment triggers its processing with the chosen payment provider, such as Stripe. @@ -103,21 +109,21 @@ Refunding payments is irreversible.
-To refund an order’s negative outstanding amount: +To refund an order's captured payment: 1. Open the order's details page. 2. Scroll to the Payment section. 3. Click on the icon at the end of the payment to refund. 4. Choose "Refund" from the dropdown. 5. In the side window that opens, fill out the following fields: - - **Select payment to refund**: Choose the payment to refund an amount from. - **Amount**: Enter the amount to refund. - - **Note**: A note that the customer can see in the notification they receive. + - **Refund Reason**: Select a reason for the refund from the dropdown. You can [manage refund reasons in the settings](../../settings/refund-reasons/page.mdx). + - **Note**: Enter a note that the customer can see in the notification they receive. 6. Once you’re done, click on the Save button. Once the payment is refunded, the customer will receive a notification about the refund and you can view the refund as part of the order's activity. -![Refund Payment Form](https://res.cloudinary.com/dza7lstvk/image/upload/v1739550855/User%20Guide/Screenshot_2025-02-14_at_6.34.01_PM_l9evmh.png) +![Refund Payment Form](https://res.cloudinary.com/dza7lstvk/image/upload/v1759995110/User%20Guide/CleanShot_2025-10-09_at_10.31.35_2x_dvbdbp.png) --- diff --git a/www/apps/user-guide/app/orders/returns/page.mdx b/www/apps/user-guide/app/orders/returns/page.mdx index fc21b0a7d0..27c10ed6e8 100644 --- a/www/apps/user-guide/app/orders/returns/page.mdx +++ b/www/apps/user-guide/app/orders/returns/page.mdx @@ -131,6 +131,6 @@ To cancel a requested return: ## Refund Customer for a Return -If a return causes an outstanding amount, you can see it in the Summary section of an order. To learn how to refund the payment, refer to [this guide](../payments/page.mdx#refund-payment). +If a return causes an outstanding amount, you can see it in the Summary section of an order. Refer to the [Manage Payments guide](../payments/page.mdx#refund-payment) to learn how to refund the customer for the return. ![Outstanding amount in summary](https://res.cloudinary.com/dza7lstvk/image/upload/v1739799808/User%20Guide/Screenshot_2025-02-17_at_3.42.49_PM_cjxgpg.png) \ No newline at end of file diff --git a/www/apps/user-guide/app/promotions/campaigns/page.mdx b/www/apps/user-guide/app/promotions/campaigns/page.mdx index d1ad31a51d..879ba21cc7 100644 --- a/www/apps/user-guide/app/promotions/campaigns/page.mdx +++ b/www/apps/user-guide/app/promotions/campaigns/page.mdx @@ -56,12 +56,22 @@ To create a campaign: - **Spend**: The total amount that can be discounted by the promotions in the campaign. Once the total amount discounted exceeds the limit, the campaign and its promotions expire. This is not applicable to "Buy X Get Y" or "Free Shipping" promotions. - If you chose the **Spend** type, select the currency of the limit in the "Currency" field. - When adding promotions to the campaign later, promotions that discount a fixed amount can only be added if the currency matches the campaign budget's currency. - - In the Limit field, you can set a campaign budget limit based on the type you chose. + - In the **Limit** field, you can set a campaign budget limit based on the type you chose. - If you chose **Usage**, enter the number of times the promotions in the campaign can be used. - If you chose **Spend**, enter the total amount that can be discounted by the promotions in the campaign. + - If you chose the **Usage** type, you can optionally set the **Limit usage per** field to apply the limitation for each customer or email. + - If you don't select any condition, the limit is applied globally. For example, if the limit is set to 5, the promotions in the campaign can be used a total of 5 times. + - If you select "Customer", the limit is applied for each customer based on their unique identifier. For example, if the limit is set to 5, each customer can use the promotions in the campaign 5 times. + - If you select "Customer Email", the limit is applied for each customer based on their email address. For example, if the limit is set to 5, each unique email address can use the promotions in the campaign 5 times. 4. Once you're done, click the Create button. -![Create Campaign Form](https://res.cloudinary.com/dza7lstvk/image/upload/v1739953632/User%20Guide/Screenshot_2025-02-19_at_10.27.00_AM_u4w38d.png) + + +The **Limit usage per** field is available from [Medusa Admin v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). If you don't see it, request your technical team to [upgrade your Medusa application](!docs!/learn/update). + + + +![Create Campaign Form](https://res.cloudinary.com/dza7lstvk/image/upload/v1760349967/User%20Guide/CleanShot_2025-10-13_at_13.05.33_2x_xq5ec5.png) --- @@ -133,6 +143,23 @@ To edit a campaign's budget usage or spending limit: --- +## Information that Can't be Edited in Campaign + +After creating a campaign, you can't edit the following information: + +1. The campaign's budget type (usage or spend). +2. The campaign's budget currency (if the type is spend). +3. The campaign's budget "limit usage per" condition (customer or customer email). + +If you need to change any of this information, you should either: + +1. [Delete the campaign](#delete-campaign). +2. Expire the campaign by [setting an end date in the past](#edit-campaign-details). + +Then, create a new campaign with the correct information and [add promotions to it](#manage-promotions-of-campaign). + +--- + ## Manage Promotions of Campaign From the campaign's details page, you can manage which promotions belong to the campaign. diff --git a/www/apps/user-guide/app/promotions/create/page.mdx b/www/apps/user-guide/app/promotions/create/page.mdx index 813f248b90..b8f65ec052 100644 --- a/www/apps/user-guide/app/promotions/create/page.mdx +++ b/www/apps/user-guide/app/promotions/create/page.mdx @@ -77,7 +77,10 @@ If you chose the "Amount off Products" promotion type in the first section, fill 7. In the Maximum Quantity field, set the maximum quantity line-item quantity that the promotion is applied to in the cart. - For example, if set to `1` and the customer has a line item with quantity two in the cart, the promotion is only applied to one of them. - This condition is applied on the quantity of every applicable item in the cart. For example, if set to `1` and the customer has two applicable items in the cart, the promotion is applied to one of each of them. -8. In the "What items will the promotion be applied to?" section, you'll specify the items in the cart that the promotion can be applied to. To add a condition, click on the "Add condition" button. For each condition: +8. In the Allocation field, choose how the discount is applied when there are multiple applicable items in the cart: + - **Each**: The discount is applied to each applicable item in the cart. + - **Once**: The discount is applied to a specific number of applicable items in the cart, which is determined by the Maximum Quantity field. +9. In the "What items will the promotion be applied to?" section, you'll specify the items in the cart that the promotion can be applied to. To add a condition, click on the "Add condition" button. For each condition: - Select the attribute in the first field. This is the attribute that the condition applies to. It can be: - **Product**: The promotion applies / doesn't apply to the specified product(s). - **Product Category**: The promotion applies / doesn't apply to the products belonging to the specified categories. @@ -92,7 +95,7 @@ If you chose the "Amount off Products" promotion type in the first section, fill - Set the Attribute to Product Category. - Set the Operator to In. - Set the Value to the Shirts product category. -9. Once you're done, click Next and move on to the [next step](#step-3-campaign). +10. Once you're done, click Next and move on to the [next step](#step-3-campaign). ![What items will the promotion be applied to section](https://res.cloudinary.com/dza7lstvk/image/upload/v1739898160/User%20Guide/Screenshot_2025-02-18_at_7.02.26_PM_izi1sv.png) @@ -130,6 +133,12 @@ If you chose the "Amount off Order" promotion type in the first section, fill ou ### c. Percentage off Product + + +If any of the fields are not visible in your admin, contact your technical team to [update your Medusa version](!docs!/learn/update). + + + If you chose the "Percentage off Product" promotion type in the first section, fill out the following in the Details step: 1. For the Method, choose how you want the promotion to be applied: @@ -158,7 +167,10 @@ If you chose the "Percentage off Product" promotion type in the first section, f 6. In the Promotion Value field, set the amount to be discounted from applicable products when the promotion is applied. The amount is in the currency you chose in the previous section. 7. In the Maximum Quantity field, set the maximum quantity of the applicable that the promotion is applied to in the cart. For example, if set to `1` and the customer has two applicable items in the cart, the promotion is only applied to one of them. -8. In the "What items will the promotion be applied to?" section, you'll specify the items in the cart that the promotion can be applied to. To add a condition, click on the "Add condition" button. For each condition: +8. In the Allocation field, choose how the discount is applied when there are multiple applicable items in the cart: + - **Each**: The discount is applied to each applicable item in the cart. + - **Once**: The discount is applied to a specific number of applicable items in the cart, which is determined by the Maximum Quantity field. +9. In the "What items will the promotion be applied to?" section, you'll specify the items in the cart that the promotion can be applied to. To add a condition, click on the "Add condition" button. For each condition: - Select the attribute in the first field. This is the attribute that the condition applies to. It can be: - **Product**: The promotion applies / doesn't apply to the specified product(s). - **Product Category**: The promotion applies / doesn't apply to the products belonging to the specified categories. @@ -173,7 +185,7 @@ If you chose the "Percentage off Product" promotion type in the first section, f - Set the Attribute to Product Category. - Set the Operator to In. - Set the Value to the Shirts product category. -9. Once you're done, click Next and move on to the [next step](#step-3-campaign). +10. Once you're done, click Next and move on to the [next step](#step-3-campaign). ![What items will the promotion be applied to section](https://res.cloudinary.com/dza7lstvk/image/upload/v1739898386/User%20Guide/Screenshot_2025-02-18_at_7.06.14_PM_bdi5jv.png) @@ -332,8 +344,8 @@ In the campaigns section, you can select whether to: If you selected a new campaign, you need to fill out the following to create a campaign and add the promotion to it: -1. In the Name field, enter the campaign's name. -2. In the Identifier field, enter a custom identifier for the campaign. This is useful if you're associating the campaign with a campaign in an external system, for example. +1. In the **Name** field, enter the campaign's name. +2. In the **Identifier** field, enter a custom identifier for the campaign. This is useful if you're associating the campaign with a campaign in an external system, for example. 3. Optionally enter a description, and start and end dates. - If a start date is specified, all promotions in the campaign can be used after the specified date. - If an end date is specified, all promotions in the campaign can't be used after the specified date. @@ -341,7 +353,17 @@ If you selected a new campaign, you need to fill out the following to create a c - For Type, you can choose to set the budget either based on: - **Usage**: How many times the promotions in the campaign can be used. Once the number of times exceed the limit, the campaign and its promotions expire. - **Spend**: The total amount that can be discounted by the promotions in the campaign. Once the total amount discounted exceeds the limit, the campaign and its promotions expire. This is disabled if you select the "Buy X Get Y" or "Free Shipping" promotion types. - - In the Limit field, enter the budget limit based on the chosen type. + - In the **Limit** field, enter the budget limit based on the chosen type. + - If you chose the **Usage** type, you can optionally set the **Limit usage per** field to apply the limitation for each customer or email. + - If you don't select any condition, the limit is applied globally. For example, if the limit is set to 5, the promotions in the campaign can be used a total of 5 times. + - If you select "Customer", the limit is applied for each customer based on their unique identifier. For example, if the limit is set to 5, each customer can use the promotions in the campaign 5 times. + - If you select "Customer Email", the limit is applied for each customer based on their email address. For example, if the limit is set to 5, each unique email address can use the promotions in the campaign 5 times. 5. Once you're done, click on the Save button. + + +The **Limit usage per** field is available from [Medusa Admin v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). If you don't see it, request your technical team to [upgrade your Medusa application](!docs!/learn/update). + + + You can now [manage the promotion](../manage/page.mdx) you created. \ No newline at end of file diff --git a/www/apps/user-guide/app/promotions/manage/page.mdx b/www/apps/user-guide/app/promotions/manage/page.mdx index 375a366218..7b402b77ac 100644 --- a/www/apps/user-guide/app/promotions/manage/page.mdx +++ b/www/apps/user-guide/app/promotions/manage/page.mdx @@ -69,6 +69,12 @@ A promotion's status is shown in the Promotions listing page, and in the promoti ## Edit Promotion's Details + + +If any of the fields are not visible in your admin, contact your technical team to [update your Medusa version](!docs!/learn/update). + + + To edit a promotion's general details: 1. Go to the promotion's details page. @@ -78,8 +84,12 @@ To edit a promotion's general details: - You can change the promotion's status to either **Draft**, **Active**, or **Inactive**. Inactive is useful if the promotion was originally active but you want to expire it. - You can edit the promotion's method (whether it's applied manually or automatically) and code. - Based on the promotion's type, you can change how the amount is discounted, either by a fixed amount or a percentage. You can also change the discounted amount. - - Based on the promotion's type, you can change how the promotion is applied, either on every applicable item or across all items in the cart. - - For example, if your promotion discounts an amount from applicable products in a cart, choosing **Each** applies the discount on each applicable product, whereas **Across** applies the discount across all applicable products. + - Based on the promotion's type, you can change how the promotion is applied: + - **Each**: The discount is applied to each applicable item in the cart. + - **Across**: The discount is applied across all applicable items in the cart. + - **Once**: The discount is applied to a specific number of applicable items in the cart, which is determined by the Maximum Quantity field. + - Based on the promotion's type, some of the above options may not be available. For example, for "Amount off order", only "Across" is available. + - You can edit the maximum quantity of applicable items that the promotion is applied to in the cart. 5. Once you're done, click the Save button. ![Edit Promotion Form](https://res.cloudinary.com/dza7lstvk/image/upload/v1739951499/User%20Guide/Screenshot_2025-02-19_at_9.51.26_AM_zylmig.png) diff --git a/www/apps/user-guide/app/settings/developer/secret-api-keys/page.mdx b/www/apps/user-guide/app/settings/developer/secret-api-keys/page.mdx index 39b3959094..896d745af8 100644 --- a/www/apps/user-guide/app/settings/developer/secret-api-keys/page.mdx +++ b/www/apps/user-guide/app/settings/developer/secret-api-keys/page.mdx @@ -44,7 +44,13 @@ Here, you can see a list of all the secret API keys for the logged-in user. You ## Create Secret API Key -When you create a secret API key, you create it for the currently logged-in user. A user can have one active secret key at a time. So, if you already have one, you must [revoke it](#revoke-secret-api-key) before creating a new one. +When you create a secret API key, you create it for the currently logged-in user. + + + +Prior to [Medusa v2.11.0], users could only have one active secret API key at a time. To create a new secret API key, you had to [revoke](#revoke-secret-api-key) the existing key first. If you can't create multiple secret API keys, request your technical team to [upgrade your Medusa application](!docs!/learn/update). + + To create a new secret API key for the currently logged-in user: diff --git a/www/apps/user-guide/app/settings/locations-and-shipping/locations/page.mdx b/www/apps/user-guide/app/settings/locations-and-shipping/locations/page.mdx index 545d4089a6..b0d712efe6 100644 --- a/www/apps/user-guide/app/settings/locations-and-shipping/locations/page.mdx +++ b/www/apps/user-guide/app/settings/locations-and-shipping/locations/page.mdx @@ -52,7 +52,7 @@ To create a stock location: To view the details of a location: 1. Go to Settings β†’ Locations. -2. Click on "View details" in the header of the location's section. +2. Click on the location you want to view. This opens the location's details page where you can also manage the location. diff --git a/www/apps/user-guide/app/settings/locations-and-shipping/page.mdx b/www/apps/user-guide/app/settings/locations-and-shipping/page.mdx index a0c7cda498..ab58797700 100644 --- a/www/apps/user-guide/app/settings/locations-and-shipping/page.mdx +++ b/www/apps/user-guide/app/settings/locations-and-shipping/page.mdx @@ -19,19 +19,31 @@ export const metadata = { In the Locations & Shipping settings, you can manage stock locations and their shipping options, and other shipping-related configurations, such as shipping profiles. +## Overview + +### Stock Locations + A stock location: - Is associated with sales channels. Orders from these sales channels are fulfilled from this location. - Has fulfillment providers that can be used in that location. For example, you can use UPS in the United States and DHL in Europe. - Has shipping and pick up modes that you can add shipping options to. These are the shipping options used to fulfill items from this stock location. +### Shipping Profiles + A shipping profile groups similar products that require a different way of fulfillment. For example, fragile products are fulfilled differently than normal products. You can then provide shipping options specific for products that belong to the Fragile shipping profile. +### Shipping Option Types + A shipping option type groups shipping options with similar characteristics. For example, you can group all express shipping options together and apply a promotion to all of them at once. +--- + +## View Location & Shipping Settings + To view location and shipping settings, go to Settings β†’ Locations & Shipping. -![Location & shipping settings](https://res.cloudinary.com/dza7lstvk/image/upload/v1756389687/User%20Guide/CleanShot_2025-08-28_at_17.00.53_2x_kymfeh.png) +![Location & shipping settings](https://res.cloudinary.com/dza7lstvk/image/upload/v1759995534/User%20Guide/CleanShot_2025-10-09_at_10.38.39_2x_eqsrxl.png) --- diff --git a/www/apps/user-guide/app/settings/page.mdx b/www/apps/user-guide/app/settings/page.mdx index 7370c2076e..1504eb167c 100644 --- a/www/apps/user-guide/app/settings/page.mdx +++ b/www/apps/user-guide/app/settings/page.mdx @@ -27,6 +27,7 @@ You can also go back to the main sidebar by clicking the + +Refund reason management is available from [Medusa Admin v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). If you don't see the Refund Reasons option in your Medusa Admin, request your technical team to [upgrade your Medusa application](!docs!/learn/update). + + + +## What's a Refund Reason? + +A refund reason is a predefined reason for refunding an order. It helps you categorize and track refunds in your store. + +When [processing a refund](../../orders/payments/page.mdx#refund-payment), you can select a reason from the list of refund reasons you have set up. + +--- + +## View Refund Reasons + +To view refund reasons in your store, go to Settings β†’ Refund Reasons. Here, you can see a list of all the refund reasons you have set up in your store. You can also search, filter, and sort the refund reasons to find the one you are looking for. + +Medusa adds some default refund reasons to your store. You can use or [delete](#delete-a-refund-reason) them. + +![Refund reasons list](https://res.cloudinary.com/dza7lstvk/image/upload/v1759994483/User%20Guide/CleanShot_2025-10-09_at_10.20.53_2x_sowoag.png) + +--- + +## Create Refund Reason + +To create a refund reason: + +1. Go to Settings β†’ Refund Reasons. +2. Click the Create button in the main section's header. +3. In the form that opens: + - **Label**: Enter what is shown to customers or admin users. + - **Code**: Enter a unique identifier for the reason that's used internally. It's recommended to use only lowercase letters and underscores (`_`). For example, `pricing_error`. + - **Description**: Enter an optional description for the refund reason. +4. Once done, click the Save button. + +![Create refund reason form](https://res.cloudinary.com/dza7lstvk/image/upload/v1759994670/User%20Guide/CleanShot_2025-10-09_at_10.24.22_2x_mmkr8z.png) + +--- + +## Edit a Refund Reason + +To edit a refund reason: + +1. Go to Settings β†’ Refund Reasons. +2. Find the refund reason to edit and click the icon in its row. +3. Choose Edit from the dropdown. +4. In the side window that opens, you can edit the refund reason's code, label, and description. +5. Once you're done, click the Save button. + +![Edit refund reason form](https://res.cloudinary.com/dza7lstvk/image/upload/v1759994731/User%20Guide/CleanShot_2025-10-09_at_10.25.22_2x_hyo1j3.png) + +--- + +## Delete a Refund Reason + + + +Deleting a refund reason is irreversible. + + + +To delete a refund reason: + +1. Go to Settings β†’ Refund Reasons. +2. Find the refund reason to delete and click the icon in its row. +3. Choose Delete from the dropdown. +4. Confirm deleting the refund reason by clicking the Delete button in the pop-up. diff --git a/www/apps/user-guide/app/tips/languages/page.mdx b/www/apps/user-guide/app/tips/languages/page.mdx index 8e35f91f2c..9d3265cf8f 100644 --- a/www/apps/user-guide/app/tips/languages/page.mdx +++ b/www/apps/user-guide/app/tips/languages/page.mdx @@ -18,6 +18,18 @@ To learn how to change the language, refer to the [Manage Profile](../../setting --- +## Right-to-Left (RTL) Language Support + + + +Right-to-left (RTL) language support is available from [Medusa Admin v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). If your dashboard doesn't adjust to RTL languages, request your technical team to [upgrade your Medusa application](!docs!/learn/update). + + + +When you change the language to a right-to-left (RTL) language, such as Arabic or Hebrew, the Medusa Admin interface automatically adjusts to support RTL text direction. This includes aligning text and UI elements appropriately for better readability and user experience. + +--- + ## Official Languages - English diff --git a/www/apps/user-guide/generated/edit-dates.mjs b/www/apps/user-guide/generated/edit-dates.mjs index fef5dfd342..367bb09bd1 100644 --- a/www/apps/user-guide/generated/edit-dates.mjs +++ b/www/apps/user-guide/generated/edit-dates.mjs @@ -1,11 +1,11 @@ export const generatedEditDates = { - "app/tips/languages/page.mdx": "2025-06-10T13:21:36.282Z", + "app/tips/languages/page.mdx": "2025-10-09T07:53:20.296Z", "app/tips/bulk-editor/page.mdx": "2025-04-07T13:20:09.040Z", "app/tips/lists/page.mdx": "2025-02-14T14:16:04.867Z", "app/settings/sales-channels/page.mdx": "2025-05-30T13:31:09.754Z", "app/settings/users/page.mdx": "2025-05-30T13:31:29.991Z", "app/page.mdx": "2025-02-26T14:18:27.755Z", - "app/settings/page.mdx": "2025-08-28T13:59:06.712Z", + "app/settings/page.mdx": "2025-10-09T07:25:58.554Z", "app/products/export/page.mdx": "2025-07-31T14:29:30.511Z", "app/settings/return-reasons/page.mdx": "2025-05-30T13:31:05.596Z", "app/settings/regions/page.mdx": "2025-05-30T13:30:59.228Z", @@ -22,7 +22,7 @@ export const generatedEditDates = { "app/inventory/inventory/page.mdx": "2025-05-30T13:27:17.017Z", "app/customers/groups/page.mdx": "2025-05-30T13:26:56.820Z", "app/orders/manage/page.mdx": "2025-09-09T06:46:05.439Z", - "app/orders/returns/page.mdx": "2025-05-30T13:28:28.116Z", + "app/orders/returns/page.mdx": "2025-10-09T07:33:10.189Z", "app/inventory/page.mdx": "2025-05-30T13:27:31.179Z", "app/orders/claims/page.mdx": "2025-05-30T13:27:39.540Z", "app/orders/fulfillments/page.mdx": "2025-05-30T13:28:08.998Z", @@ -31,7 +31,7 @@ export const generatedEditDates = { "app/products/collections/page.mdx": "2025-05-30T13:29:05.948Z", "app/customers/manage/page.mdx": "2025-07-31T14:28:07.889Z", "app/discounts/create/page.mdx": "2024-05-03T17:36:38+03:00", - "app/orders/payments/page.mdx": "2025-08-26T09:54:18.417Z", + "app/orders/payments/page.mdx": "2025-10-09T07:31:59.781Z", "app/discounts/page.mdx": "2024-05-03T17:36:38+03:00", "app/orders/exchanges/page.mdx": "2025-05-30T13:27:55.646Z", "app/products/create/page.mdx": "2025-05-30T13:29:24.876Z", @@ -39,16 +39,16 @@ export const generatedEditDates = { "app/products/variants/page.mdx": "2025-05-30T13:29:45.049Z", "app/products/create/bundle/page.mdx": "2025-05-30T13:29:15.958Z", "app/products/create/multi-part/page.mdx": "2025-05-30T13:29:20.321Z", - "app/promotions/campaigns/page.mdx": "2025-05-30T13:29:54.241Z", - "app/promotions/create/page.mdx": "2025-10-01T09:51:15.062Z", - "app/promotions/manage/page.mdx": "2025-05-30T13:30:02.678Z", + "app/promotions/campaigns/page.mdx": "2025-10-13T10:14:17.948Z", + "app/promotions/create/page.mdx": "2025-10-14T12:09:22.188Z", + "app/promotions/manage/page.mdx": "2025-10-14T12:09:22.188Z", "app/promotions/page.mdx": "2025-05-30T13:30:08.538Z", "app/price-lists/create/page.mdx": "2025-05-30T13:28:41.126Z", "app/price-lists/manage/page.mdx": "2025-05-30T13:28:47.929Z", "app/price-lists/page.mdx": "2025-05-30T13:28:53.668Z", "app/settings/tax-regions/page.mdx": "2025-05-30T13:31:21.021Z", - "app/settings/locations-and-shipping/locations/page.mdx": "2025-08-28T14:06:20.266Z", - "app/settings/locations-and-shipping/page.mdx": "2025-08-28T14:01:34.185Z", + "app/settings/locations-and-shipping/locations/page.mdx": "2025-10-09T07:38:11.270Z", + "app/settings/locations-and-shipping/page.mdx": "2025-10-09T07:40:33.084Z", "app/settings/locations-and-shipping/shipping-profiles/page.mdx": "2025-05-30T13:30:37.452Z", "app/settings/product-tags/page.mdx": "2025-05-30T13:30:46.705Z", "app/settings/product-types/page.mdx": "2025-05-30T13:30:50.623Z", @@ -59,5 +59,6 @@ export const generatedEditDates = { "app/orders/draft-orders/create/page.mdx": "2025-08-26T09:34:53.621Z", "app/orders/draft-orders/manage/page.mdx": "2025-08-26T09:47:17.568Z", "app/orders/draft-orders/page.mdx": "2025-08-26T09:31:53.784Z", - "app/settings/locations-and-shipping/shipping-option-types/page.mdx": "2025-08-28T14:03:13.999Z" + "app/settings/locations-and-shipping/shipping-option-types/page.mdx": "2025-08-28T14:03:13.999Z", + "app/settings/refund-reasons/page.mdx": "2025-10-09T07:29:52.837Z" } \ No newline at end of file diff --git a/www/apps/user-guide/generated/sidebar.mjs b/www/apps/user-guide/generated/sidebar.mjs index 2f38d6af50..431a035e73 100644 --- a/www/apps/user-guide/generated/sidebar.mjs +++ b/www/apps/user-guide/generated/sidebar.mjs @@ -443,6 +443,14 @@ export const generatedSidebars = [ "path": "/settings/return-reasons", "children": [] }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "title": "Refund Reasons", + "path": "/settings/refund-reasons", + "children": [] + }, { "loaded": true, "isPathHref": true, diff --git a/www/apps/user-guide/sidebar.mjs b/www/apps/user-guide/sidebar.mjs index 6a55e4bff3..95c0181e8b 100644 --- a/www/apps/user-guide/sidebar.mjs +++ b/www/apps/user-guide/sidebar.mjs @@ -273,6 +273,11 @@ export const sidebar = [ title: "Return Reasons", path: "/settings/return-reasons", }, + { + type: "link", + title: "Refund Reasons", + path: "/settings/refund-reasons", + }, { type: "link", title: "Sales Channels", diff --git a/www/packages/tags/src/tags/order.ts b/www/packages/tags/src/tags/order.ts index b31655f959..1f3c07e4cc 100644 --- a/www/packages/tags/src/tags/order.ts +++ b/www/packages/tags/src/tags/order.ts @@ -43,6 +43,10 @@ export const order = [ "title": "Manage Order Returns", "path": "https://docs.medusajs.com/user-guide/orders/returns" }, + { + "title": "Manage Refund Reasons", + "path": "https://docs.medusajs.com/user-guide/settings/refund-reasons" + }, { "title": "Manage Return Reasons", "path": "https://docs.medusajs.com/user-guide/settings/return-reasons" diff --git a/www/packages/tags/src/tags/user-guide.ts b/www/packages/tags/src/tags/user-guide.ts index 9257da57fa..dd224c695d 100644 --- a/www/packages/tags/src/tags/user-guide.ts +++ b/www/packages/tags/src/tags/user-guide.ts @@ -179,6 +179,10 @@ export const userGuide = [ "title": "Manage Profile", "path": "https://docs.medusajs.com/user-guide/settings/profile" }, + { + "title": "Manage Refund Reasons", + "path": "https://docs.medusajs.com/user-guide/settings/refund-reasons" + }, { "title": "Manage Regions", "path": "https://docs.medusajs.com/user-guide/settings/regions"