customer-registration 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
- const i18nTranslations0 = {};
3
2
  const widgetModule = { widgets: [] };
4
3
  const routeModule = {
5
4
  routes: []
@@ -11,7 +10,7 @@ const formModule = { customFields: {} };
11
10
  const displayModule = {
12
11
  displays: {}
13
12
  };
14
- const i18nModule = { resources: i18nTranslations0 };
13
+ const i18nModule = { resources: {} };
15
14
  const plugin = {
16
15
  widgetModule,
17
16
  routeModule,
@@ -1,4 +1,3 @@
1
- const i18nTranslations0 = {};
2
1
  const widgetModule = { widgets: [] };
3
2
  const routeModule = {
4
3
  routes: []
@@ -10,7 +9,7 @@ const formModule = { customFields: {} };
10
9
  const displayModule = {
11
10
  displays: {}
12
11
  };
13
- const i18nModule = { resources: i18nTranslations0 };
12
+ const i18nModule = { resources: {} };
14
13
  const plugin = {
15
14
  widgetModule,
16
15
  routeModule,
@@ -47,4 +47,4 @@ async function POST(req, res) {
47
47
  }
48
48
  }
49
49
  }
50
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL2N1c3RvbWVycy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQW1CQSxvQkE0REM7QUE3RUQscURBQW1EO0FBaUI1QyxLQUFLLFVBQVUsSUFBSSxDQUN4QixHQUFrQixFQUNsQixHQUFtQjtJQUVuQixNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGVBQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUNqRSxNQUFNLElBQUksR0FBRyxHQUFHLENBQUMsSUFBaUMsQ0FBQTtJQUVsRCxJQUFJLENBQUM7UUFDSCwwQ0FBMEM7UUFDMUMsTUFBTSxFQUNKLEtBQUssRUFDTCxVQUFVLEVBQ1YsU0FBUyxFQUNULEtBQUssRUFDTCxZQUFZLEVBQ1osUUFBUSxFQUNSLGNBQWMsRUFBRSxzQkFBc0IsRUFDdEMsY0FBYyxFQUFFLHNCQUFzQixFQUN0QyxHQUFHLElBQUksRUFDUixHQUFHLElBQUksQ0FBQTtRQUVSLDREQUE0RDtRQUM1RCxNQUFNLFlBQVksR0FBOEI7WUFDOUMsR0FBRyxJQUFJO1lBQ1AsS0FBSztZQUNMLFVBQVU7WUFDVixTQUFTO1lBQ1QsS0FBSztZQUNMLFlBQVk7WUFDWixRQUFRLEVBQUUsUUFBUSxJQUFJLEVBQUU7WUFDeEIsY0FBYyxFQUFFLEtBQUs7WUFDckIsY0FBYyxFQUFFLEtBQUs7U0FDdEIsQ0FBQTtRQUVELHdEQUF3RDtRQUN4RCxtQ0FBbUM7UUFDbkMsTUFBTSxTQUFTLEdBQUcsTUFBTSxxQkFBcUIsQ0FBQyxlQUFlLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFBO1FBQzdFLE1BQU0sUUFBUSxHQUFHLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtRQUU3QiwwQ0FBMEM7UUFDMUMsTUFBTSxlQUFlLEdBQUcsTUFBTSxxQkFBcUIsQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUE7UUFFakYsNkNBQTZDO1FBQzdDLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO1lBQ25CLFFBQVEsRUFBRSxlQUFlO1NBQzFCLENBQUMsQ0FBQTtJQUNKLENBQUM7SUFBQyxPQUFPLEtBQVUsRUFBRSxDQUFDO1FBQ3BCLDhCQUE4QjtRQUM5QixJQUFJLEtBQUssQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLGdCQUFnQixDQUFDLElBQUksS0FBSyxDQUFDLE9BQU8sRUFBRSxRQUFRLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztZQUN0RixHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQztnQkFDbkIsT0FBTyxFQUFFLDJDQUEyQztnQkFDcEQsSUFBSSxFQUFFLGlCQUFpQjthQUN4QixDQUFDLENBQUE7UUFDSixDQUFDO2FBQU0sQ0FBQztZQUNOLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO2dCQUNuQixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU8sSUFBSSwyQkFBMkI7Z0JBQ3JELElBQUksRUFBRSxjQUFjO2FBQ3JCLENBQUMsQ0FBQTtRQUNKLENBQUM7SUFDSCxDQUFDO0FBQ0gsQ0FBQyJ9
50
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL2N1c3RvbWVycy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQW1CQSxvQkE0REM7QUE3RUQscURBQW1EO0FBaUI1QyxLQUFLLFVBQVUsSUFBSSxDQUN4QixHQUFrQixFQUNsQixHQUFtQjtJQUVuQixNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGVBQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUNqRSxNQUFNLElBQUksR0FBRyxHQUFHLENBQUMsSUFBaUMsQ0FBQTtJQUVsRCxJQUFJLENBQUM7UUFDSCwwQ0FBMEM7UUFDMUMsTUFBTSxFQUNKLEtBQUssRUFDTCxVQUFVLEVBQ1YsU0FBUyxFQUNULEtBQUssRUFDTCxZQUFZLEVBQ1osUUFBUSxFQUNSLGNBQWMsRUFBRSxzQkFBc0IsRUFDdEMsY0FBYyxFQUFFLHNCQUFzQixFQUN0QyxHQUFHLElBQUksRUFDUixHQUFHLElBQUksQ0FBQTtRQUVSLDREQUE0RDtRQUM1RCxNQUFNLFlBQVksR0FBOEI7WUFDOUMsR0FBRyxJQUFJO1lBQ1AsS0FBSztZQUNMLFVBQVU7WUFDVixTQUFTO1lBQ1QsS0FBSztZQUNMLFlBQVk7WUFDWixRQUFRLEVBQUUsUUFBUSxJQUFJLEVBQUU7WUFDeEIsY0FBYyxFQUFFLEtBQUs7WUFDckIsY0FBYyxFQUFFLEtBQUs7U0FDdEIsQ0FBQTtRQUVELHdEQUF3RDtRQUN4RCxtQ0FBbUM7UUFDbkMsTUFBTSxTQUFTLEdBQUcsTUFBTSxxQkFBcUIsQ0FBQyxlQUFlLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFBO1FBQzdFLE1BQU0sUUFBUSxHQUFHLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtRQUU3QiwwQ0FBMEM7UUFDMUMsTUFBTSxlQUFlLEdBQUcsTUFBTSxxQkFBcUIsQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUE4QixDQUFBO1FBRTlHLDZDQUE2QztRQUM3QyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQztZQUNuQixRQUFRLEVBQUUsZUFBZTtTQUMxQixDQUFDLENBQUE7SUFDSixDQUFDO0lBQUMsT0FBTyxLQUFVLEVBQUUsQ0FBQztRQUNwQiw4QkFBOEI7UUFDOUIsSUFBSSxLQUFLLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLEtBQUssQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUM7WUFDdEYsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUM7Z0JBQ25CLE9BQU8sRUFBRSwyQ0FBMkM7Z0JBQ3BELElBQUksRUFBRSxpQkFBaUI7YUFDeEIsQ0FBQyxDQUFBO1FBQ0osQ0FBQzthQUFNLENBQUM7WUFDTixHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQztnQkFDbkIsT0FBTyxFQUFFLEtBQUssQ0FBQyxPQUFPLElBQUksMkJBQTJCO2dCQUNyRCxJQUFJLEVBQUUsY0FBYzthQUNyQixDQUFDLENBQUE7UUFDSixDQUFDO0lBQ0gsQ0FBQztBQUNILENBQUMifQ==
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Migration20250118000000 = void 0;
4
+ const migrations_1 = require("@medusajs/framework/mikro-orm/migrations");
5
+ const TABLE_NAME = "customer";
6
+ const EMAIL_COLUMN = "email_verified";
7
+ const PHONE_COLUMN = "phone_verified";
8
+ class Migration20250118000000 extends migrations_1.Migration {
9
+ async up() {
10
+ this.addSql(`alter table "${TABLE_NAME}"
11
+ add column if not exists "${EMAIL_COLUMN}" boolean not null default false,
12
+ add column if not exists "${PHONE_COLUMN}" boolean not null default false;`);
13
+ }
14
+ async down() {
15
+ this.addSql(`alter table "${TABLE_NAME}"
16
+ drop column if exists "${EMAIL_COLUMN}",
17
+ drop column if exists "${PHONE_COLUMN}";`);
18
+ }
19
+ }
20
+ exports.Migration20250118000000 = Migration20250118000000;
21
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNTAxMTgwMDAwMDBBZGRFbWFpbFZlcmlmaWVkQ29sdW1uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL21vZHVsZXMvY3VzdG9tZXItcmVnaXN0cmF0aW9uL21pZ3JhdGlvbnMvTWlncmF0aW9uMjAyNTAxMTgwMDAwMDBBZGRFbWFpbFZlcmlmaWVkQ29sdW1uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHlFQUFvRTtBQUVwRSxNQUFNLFVBQVUsR0FBRyxVQUFVLENBQUE7QUFDN0IsTUFBTSxZQUFZLEdBQUcsZ0JBQWdCLENBQUE7QUFDckMsTUFBTSxZQUFZLEdBQUcsZ0JBQWdCLENBQUE7QUFFckMsTUFBYSx1QkFBd0IsU0FBUSxzQkFBUztJQUNwRCxLQUFLLENBQUMsRUFBRTtRQUNOLElBQUksQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLFVBQVU7a0NBQ1IsWUFBWTtrQ0FDWixZQUFZLG1DQUFtQyxDQUFDLENBQUE7SUFDaEYsQ0FBQztJQUVELEtBQUssQ0FBQyxJQUFJO1FBQ1IsSUFBSSxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsVUFBVTsrQkFDWCxZQUFZOytCQUNaLFlBQVksSUFBSSxDQUFDLENBQUE7SUFDOUMsQ0FBQztDQUNGO0FBWkQsMERBWUMifQ==
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
- # Medusa Plugin: Customer Email Verified Registration Override
1
+ # Medusa Plugin: Customer Registration Guard
2
2
 
3
- A Medusa v2 plugin that overrides the customer registration endpoint to set `email_verified = false` by default.
3
+ A lean Medusa v2 plugin that overrides the store customer registration endpoint and guarantees both `email_verified` and `phone_verified` start as `false`.
4
4
 
5
5
  ## Features
6
6
 
7
- - Overrides the `POST /store/customers` registration endpoint
8
- - Forces `email_verified = false` and `phone_verified = false` for every new customer no direct DB mutation required
9
- - Emits a notification-driven welcome email every time a customer registers successfully
10
- - Maintains all existing customer registration functionality
7
+ - Overrides `POST /store/customers` with a drop-in route handler.
8
+ - Forces `email_verified = false` and `phone_verified = false` for every new customer, regardless of the request payload.
9
+ - Ships a lightweight Medusa module (`customer-registration`) so the bundled Mikro-ORM migration automatically runs during `npx medusa db:migrate`.
10
+ - Adds both verification columns if they’re missing, ensuring downstream logic can rely on them.
11
+ - No custom modules, admin extensions, subscribers, or providers — just the pieces required for registration hardening.
11
12
 
12
13
  ## Installation
13
14
 
@@ -52,28 +53,21 @@ yarn dev
52
53
 
53
54
  ## Usage
54
55
 
55
- ### Registration
56
+ ### Registration override
56
57
 
57
- When a customer registers via `POST /store/customers`, the plugin automatically forces `email_verified = false` before persisting the customer with the Customer Module. No raw SQL access is required.
58
-
59
- ### Welcome email subscriber
60
-
61
- - A subscriber listening on `customer.created` collects the newly created customer(s) and calls the Notification Module to dispatch an email using the template ID stored in `CUSTOMER_REGISTRATION_WELCOME_TEMPLATE_ID` (defaults to `customer-registration-success`).
62
- - Configure the Notification Module in your Medusa project and ensure at least one provider handles the `email` channel for the template ID above.
63
- - The notification payload includes the customer's `email`, `first_name`, `last_name`, and `full_name`, so templates can be fully personalized.
58
+ When a shopper registers via `POST /store/customers`, the plugin intercepts the request, normalizes the payload, and always persists `email_verified = false` and `phone_verified = false`. The rest of Medusa’s default behavior is preserved by deferring to the Customer Module service.
64
59
 
65
60
  ### Database migration
66
61
 
67
- - The plugin ships with a Mikro-ORM migration (`Migration20250118000000AddEmailVerifiedColumn`) that adds both `email_verified BOOLEAN NOT NULL DEFAULT FALSE` and `phone_verified BOOLEAN NOT NULL DEFAULT FALSE` columns to the core `customer` table.
68
- - After installing or updating the plugin, run `npx medusa db:migrate` (or the equivalent package manager command) in your Medusa project to apply the migration before handling registrations.
62
+ - A module-scoped migration (`Migration20250118000000AddEmailVerifiedColumn`) lives under `src/modules/customer-registration/migrations`. Medusa automatically discovers it when the plugin is registered.
63
+ - After installing or updating the plugin, run `npx medusa db:migrate` in your Medusa project so the columns exist before handling registrations.
69
64
 
70
65
  ## Requirements
71
66
 
72
67
  - Medusa v2.11.2 or higher
73
- - Customer table must have `email_verified` and `phone_verified` boolean columns (default: `false`)
74
- - Run the plugin-provided migration so the columns are created (`npx medusa db:migrate`)
75
- - Notification Module configured with at least one provider that supports the `email` channel
76
- - `CUSTOMER_REGISTRATION_WELCOME_TEMPLATE_ID` set in your `.env`/deployment (optional – falls back to `customer-registration-success`)
68
+ - Customer table must have `email_verified` and `phone_verified` boolean columns (default: `false`) — the provided migration handles this.
69
+ - Run `npx medusa db:migrate` after installation so the module migration is applied.
70
+ - No additional providers, subscribers, or admin extensions are required.
77
71
 
78
72
  ## Development
79
73
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "customer-registration",
3
- "version": "0.0.20",
4
- "description": "Medusa plugin to override customer registration endpoint to set email_verified = false by default.",
3
+ "version": "0.0.22",
4
+ "description": "Medusa plugin that overrides store customer registration and enforces email/phone verification flags.",
5
5
  "author": "Medusa (https://medusajs.com)",
6
6
  "license": "MIT",
7
7
  "files": [
@@ -9,16 +9,9 @@
9
9
  ],
10
10
  "exports": {
11
11
  "./package.json": "./package.json",
12
- "./workflows": "./.medusa/server/src/workflows/index.js",
13
12
  "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
14
13
  "./modules/*": "./.medusa/server/src/modules/*/index.js",
15
- "./providers/*": "./.medusa/server/src/providers/*/index.js",
16
- "./*": "./.medusa/server/src/*.js",
17
- "./admin": {
18
- "import": "./.medusa/server/src/admin/index.mjs",
19
- "require": "./.medusa/server/src/admin/index.js",
20
- "default": "./.medusa/server/src/admin/index.js"
21
- }
14
+ "./*": "./.medusa/server/src/*.js"
22
15
  },
23
16
  "keywords": [
24
17
  "medusa",
@@ -30,37 +23,19 @@
30
23
  "scripts": {
31
24
  "build": "medusa plugin:build",
32
25
  "dev": "medusa plugin:develop",
33
- "prepublishOnly": "medusa plugin:build",
34
- "publish": "npx medusa plugin:publish"
26
+ "prepublishOnly": "medusa plugin:build"
35
27
  },
36
28
  "devDependencies": {
37
- "@medusajs/admin-sdk": "2.11.2",
38
29
  "@medusajs/cli": "2.11.2",
39
30
  "@medusajs/framework": "2.11.2",
40
- "@medusajs/icons": "2.11.2",
41
31
  "@medusajs/medusa": "2.11.2",
42
- "@medusajs/test-utils": "2.11.2",
43
- "@medusajs/ui": "4.0.25",
44
- "@swc/core": "1.5.7",
45
- "@types/node": "^20.0.0",
46
- "@types/react": "^18.3.2",
47
- "@types/react-dom": "^18.2.25",
48
- "prop-types": "^15.8.1",
49
- "react": "^18.2.0",
50
- "react-dom": "^18.2.0",
51
32
  "ts-node": "^10.9.2",
52
- "typescript": "^5.6.2",
53
- "vite": "^5.2.11",
54
- "yalc": "^1.0.0-pre.53"
33
+ "typescript": "^5.6.2"
55
34
  },
56
35
  "peerDependencies": {
57
- "@medusajs/admin-sdk": "2.11.2",
58
36
  "@medusajs/cli": "2.11.2",
59
37
  "@medusajs/framework": "2.11.2",
60
- "@medusajs/icons": "2.11.2",
61
- "@medusajs/medusa": "2.11.2",
62
- "@medusajs/test-utils": "2.11.2",
63
- "@medusajs/ui": "4.0.25"
38
+ "@medusajs/medusa": "2.11.2"
64
39
  },
65
40
  "engines": {
66
41
  "node": ">=20"
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GET = GET;
4
- async function GET(req, res) {
5
- res.sendStatus(200);
6
- }
7
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3BsdWdpbi9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUVBLGtCQUtDO0FBTE0sS0FBSyxVQUFVLEdBQUcsQ0FDdkIsR0FBa0IsRUFDbEIsR0FBbUI7SUFFbkIsR0FBRyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUN0QixDQUFDIn0=
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GET = GET;
4
- async function GET(req, res) {
5
- res.sendStatus(200);
6
- }
7
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL3BsdWdpbi9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUVBLGtCQUtDO0FBTE0sS0FBSyxVQUFVLEdBQUcsQ0FDdkIsR0FBa0IsRUFDbEIsR0FBbUI7SUFFbkIsR0FBRyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUN0QixDQUFDIn0=
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Migration20250118000000 = void 0;
4
- const migrations_1 = require("@medusajs/framework/mikro-orm/migrations");
5
- const TABLE_NAME = "customer";
6
- const EMAIL_COLUMN = "email_verified";
7
- const PHONE_COLUMN = "phone_verified";
8
- class Migration20250118000000 extends migrations_1.Migration {
9
- async up() {
10
- this.addSql(`alter table "${TABLE_NAME}"
11
- add column if not exists "${EMAIL_COLUMN}" boolean not null default false,
12
- add column if not exists "${PHONE_COLUMN}" boolean not null default false;`);
13
- }
14
- async down() {
15
- this.addSql(`alter table "${TABLE_NAME}"
16
- drop column if exists "${EMAIL_COLUMN}",
17
- drop column if exists "${PHONE_COLUMN}";`);
18
- }
19
- }
20
- exports.Migration20250118000000 = Migration20250118000000;
21
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNTAxMTgwMDAwMDBBZGRFbWFpbFZlcmlmaWVkQ29sdW1uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL21pZ3JhdGlvbnMvTWlncmF0aW9uMjAyNTAxMTgwMDAwMDBBZGRFbWFpbFZlcmlmaWVkQ29sdW1uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHlFQUFvRTtBQUVwRSxNQUFNLFVBQVUsR0FBRyxVQUFVLENBQUE7QUFDN0IsTUFBTSxZQUFZLEdBQUcsZ0JBQWdCLENBQUE7QUFDckMsTUFBTSxZQUFZLEdBQUcsZ0JBQWdCLENBQUE7QUFFckMsTUFBYSx1QkFBd0IsU0FBUSxzQkFBUztJQUMzQyxLQUFLLENBQUMsRUFBRTtRQUNmLElBQUksQ0FBQyxNQUFNLENBQ1QsZ0JBQWdCLFVBQVU7b0NBQ0ksWUFBWTtvQ0FDWixZQUFZLG1DQUFtQyxDQUM5RSxDQUFBO0lBQ0gsQ0FBQztJQUVRLEtBQUssQ0FBQyxJQUFJO1FBQ2pCLElBQUksQ0FBQyxNQUFNLENBQ1QsZ0JBQWdCLFVBQVU7aUNBQ0MsWUFBWTtpQ0FDWixZQUFZLElBQUksQ0FDNUMsQ0FBQTtJQUNILENBQUM7Q0FDRjtBQWhCRCwwREFnQkMifQ==
@@ -1,60 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.config = void 0;
4
- exports.default = handleCustomerCreated;
5
- const utils_1 = require("@medusajs/framework/utils");
6
- const WELCOME_TEMPLATE_ID = process.env.CUSTOMER_REGISTRATION_WELCOME_TEMPLATE_ID ||
7
- "customer-registration-success";
8
- async function handleCustomerCreated({ event: { data }, container, }) {
9
- const logger = container.hasRegistration(utils_1.ContainerRegistrationKeys.LOGGER)
10
- ? container.resolve(utils_1.ContainerRegistrationKeys.LOGGER)
11
- : null;
12
- if (!Array.isArray(data) || !data.length) {
13
- return;
14
- }
15
- const customerIds = data
16
- .map((payload) => payload?.id)
17
- .filter((id) => typeof id === "string" && !!id);
18
- if (!customerIds.length) {
19
- return;
20
- }
21
- try {
22
- const customerModuleService = container.resolve(utils_1.Modules.CUSTOMER);
23
- const customers = await customerModuleService.listCustomers({
24
- id: customerIds,
25
- });
26
- if (!customers.length) {
27
- return;
28
- }
29
- if (!container.hasRegistration(utils_1.Modules.NOTIFICATION)) {
30
- logger?.warn?.("[customer-registration] Notification module is not configured. Skipping welcome email.");
31
- return;
32
- }
33
- const notificationModuleService = container.resolve(utils_1.Modules.NOTIFICATION);
34
- await notificationModuleService.createNotifications(customers.map((customer) => ({
35
- to: customer.email,
36
- channel: "email",
37
- template: WELCOME_TEMPLATE_ID,
38
- resource_id: customer.id,
39
- resource_type: "customer",
40
- receiver_id: customer.id,
41
- trigger_type: "customer-registration",
42
- data: {
43
- email: customer.email,
44
- first_name: customer.first_name,
45
- last_name: customer.last_name,
46
- full_name: [customer.first_name, customer.last_name]
47
- .filter(Boolean)
48
- .join(" ")
49
- .trim(),
50
- },
51
- })));
52
- }
53
- catch (error) {
54
- logger?.error?.("[customer-registration] Failed to send welcome email", error);
55
- }
56
- }
57
- exports.config = {
58
- event: utils_1.CustomerWorkflowEvents.CREATED,
59
- };
60
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3VzdG9tZXItY3JlYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9zdWJzY3JpYmVycy9jdXN0b21lci1jcmVhdGVkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQW1CQSx3Q0FvRUM7QUFsRkQscURBSWtDO0FBTWxDLE1BQU0sbUJBQW1CLEdBQ3ZCLE9BQU8sQ0FBQyxHQUFHLENBQUMseUNBQXlDO0lBQ3JELCtCQUErQixDQUFBO0FBRWxCLEtBQUssVUFBVSxxQkFBcUIsQ0FBQyxFQUNsRCxLQUFLLEVBQUUsRUFBRSxJQUFJLEVBQUUsRUFDZixTQUFTLEdBQzhCO0lBQ3ZDLE1BQU0sTUFBTSxHQUFrQixTQUFTLENBQUMsZUFBZSxDQUNyRCxpQ0FBeUIsQ0FBQyxNQUFNLENBQ2pDO1FBQ0MsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsTUFBTSxDQUFDO1FBQ3JELENBQUMsQ0FBQyxJQUFJLENBQUE7SUFFUixJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUN6QyxPQUFNO0lBQ1IsQ0FBQztJQUVELE1BQU0sV0FBVyxHQUFHLElBQUk7U0FDckIsR0FBRyxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDO1NBQzdCLE1BQU0sQ0FBQyxDQUFDLEVBQUUsRUFBZ0IsRUFBRSxDQUFDLE9BQU8sRUFBRSxLQUFLLFFBQVEsSUFBSSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUE7SUFFL0QsSUFBSSxDQUFDLFdBQVcsQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUN4QixPQUFNO0lBQ1IsQ0FBQztJQUVELElBQUksQ0FBQztRQUNILE1BQU0scUJBQXFCLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxlQUFPLENBQUMsUUFBUSxDQUFDLENBQUE7UUFFakUsTUFBTSxTQUFTLEdBQUcsTUFBTSxxQkFBcUIsQ0FBQyxhQUFhLENBQUM7WUFDMUQsRUFBRSxFQUFFLFdBQVc7U0FDaEIsQ0FBQyxDQUFBO1FBRUYsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUN0QixPQUFNO1FBQ1IsQ0FBQztRQUVELElBQUksQ0FBQyxTQUFTLENBQUMsZUFBZSxDQUFDLGVBQU8sQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDO1lBQ3JELE1BQU0sRUFBRSxJQUFJLEVBQUUsQ0FDWix3RkFBd0YsQ0FDekYsQ0FBQTtZQUNELE9BQU07UUFDUixDQUFDO1FBRUQsTUFBTSx5QkFBeUIsR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLGVBQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQTtRQUV6RSxNQUFNLHlCQUF5QixDQUFDLG1CQUFtQixDQUNqRCxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQzNCLEVBQUUsRUFBRSxRQUFRLENBQUMsS0FBSztZQUNsQixPQUFPLEVBQUUsT0FBTztZQUNoQixRQUFRLEVBQUUsbUJBQW1CO1lBQzdCLFdBQVcsRUFBRSxRQUFRLENBQUMsRUFBRTtZQUN4QixhQUFhLEVBQUUsVUFBVTtZQUN6QixXQUFXLEVBQUUsUUFBUSxDQUFDLEVBQUU7WUFDeEIsWUFBWSxFQUFFLHVCQUF1QjtZQUNyQyxJQUFJLEVBQUU7Z0JBQ0osS0FBSyxFQUFFLFFBQVEsQ0FBQyxLQUFLO2dCQUNyQixVQUFVLEVBQUUsUUFBUSxDQUFDLFVBQVU7Z0JBQy9CLFNBQVMsRUFBRSxRQUFRLENBQUMsU0FBUztnQkFDN0IsU0FBUyxFQUFFLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxRQUFRLENBQUMsU0FBUyxDQUFDO3FCQUNqRCxNQUFNLENBQUMsT0FBTyxDQUFDO3FCQUNmLElBQUksQ0FBQyxHQUFHLENBQUM7cUJBQ1QsSUFBSSxFQUFFO2FBQ1Y7U0FDRixDQUFDLENBQUMsQ0FDSixDQUFBO0lBQ0gsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixNQUFNLEVBQUUsS0FBSyxFQUFFLENBQ2Isc0RBQXNELEVBQ3RELEtBQUssQ0FDTixDQUFBO0lBQ0gsQ0FBQztBQUNILENBQUM7QUFFWSxRQUFBLE1BQU0sR0FBcUI7SUFDdEMsS0FBSyxFQUFFLDhCQUFzQixDQUFDLE9BQU87Q0FDdEMsQ0FBQSJ9
@@ -1,31 +0,0 @@
1
- # Admin Customizations
2
-
3
- You can extend the Medusa Admin to add widgets and new pages. Your customizations interact with API routes to provide merchants with custom functionalities.
4
-
5
- ## Example: Create a Widget
6
-
7
- A widget is a React component that can be injected into an existing page in the admin dashboard.
8
-
9
- For example, create the file `src/admin/widgets/product-widget.tsx` with the following content:
10
-
11
- ```tsx title="src/admin/widgets/product-widget.tsx"
12
- import { defineWidgetConfig } from "@medusajs/admin-sdk"
13
-
14
- // The widget
15
- const ProductWidget = () => {
16
- return (
17
- <div>
18
- <h2>Product Widget</h2>
19
- </div>
20
- )
21
- }
22
-
23
- // The widget's configurations
24
- export const config = defineWidgetConfig({
25
- zone: "product.details.after",
26
- })
27
-
28
- export default ProductWidget
29
- ```
30
-
31
- This inserts a widget with the text “Product Widget” at the end of a product’s details page.
@@ -1,58 +0,0 @@
1
- # Admin Customizations Translations
2
-
3
- The Medusa Admin dashboard supports multiple languages for its interface. Medusa uses [react-i18next](https://react.i18next.com/) to manage translations in the admin dashboard.
4
-
5
- To add translations, create JSON translation files for each language under the `src/admin/i18n/json` directory. For example, create the `src/admin/i18n/json/en.json` file with the following content:
6
-
7
- ```json
8
- {
9
- "brands": {
10
- "title": "Brands",
11
- "description": "Manage your product brands"
12
- },
13
- "done": "Done"
14
- }
15
- ```
16
-
17
- Then, export the translations in `src/admin/i18n/index.ts`:
18
-
19
- ```ts
20
- import en from "./json/en.json" with { type: "json" }
21
-
22
- export default {
23
- en: {
24
- translation: en,
25
- },
26
- }
27
- ```
28
-
29
- Finally, use translations in your admin widgets and routes using the `useTranslation` hook:
30
-
31
- ```tsx
32
- import { defineWidgetConfig } from "@medusajs/admin-sdk"
33
- import { Button, Container, Heading } from "@medusajs/ui"
34
- import { useTranslation } from "react-i18next"
35
-
36
- const ProductWidget = () => {
37
- const { t } = useTranslation()
38
- return (
39
- <Container className="p-0">
40
- <div className="flex items-center justify-between px-6 py-4">
41
- <Heading level="h2">{t("brands.title")}</Heading>
42
- <p>{t("brands.description")}</p>
43
- </div>
44
- <div className="flex justify-end px-6 py-4">
45
- <Button variant="primary">{t("done")}</Button>
46
- </div>
47
- </Container>
48
- )
49
- }
50
-
51
- export const config = defineWidgetConfig({
52
- zone: "product.details.before",
53
- })
54
-
55
- export default ProductWidget
56
- ```
57
-
58
- Learn more about translating admin extensions in the [Translate Admin Customizations](https://docs.medusajs.com/learn/fundamentals/admin/translations) documentation.
@@ -1 +0,0 @@
1
- export default {}
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "useDefineForClassFields": true,
5
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
- "module": "ESNext",
7
- "skipLibCheck": true,
8
-
9
- /* Bundler mode */
10
- "moduleResolution": "bundler",
11
- "allowImportingTsExtensions": true,
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
14
- "noEmit": true,
15
- "jsx": "react-jsx",
16
-
17
- /* Linting */
18
- "strict": true,
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "noFallthroughCasesInSwitch": true
22
- },
23
- "include": ["."]
24
- }
@@ -1 +0,0 @@
1
- /// <reference types="vite/client" />
package/src/api/README.md DELETED
@@ -1,133 +0,0 @@
1
- # Custom API Routes
2
-
3
- An API Route is a REST API endpoint.
4
-
5
- An API Route is created in a TypeScript or JavaScript file under the `/src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.
6
-
7
- For example, to create a `GET` API Route at `/store/hello-world`, create the file `src/api/store/hello-world/route.ts` with the following content:
8
-
9
- ```ts
10
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
11
-
12
- export async function GET(req: MedusaRequest, res: MedusaResponse) {
13
- res.json({
14
- message: "Hello world!",
15
- });
16
- }
17
- ```
18
-
19
- ## Supported HTTP methods
20
-
21
- The file based routing supports the following HTTP methods:
22
-
23
- - GET
24
- - POST
25
- - PUT
26
- - PATCH
27
- - DELETE
28
- - OPTIONS
29
- - HEAD
30
-
31
- You can define a handler for each of these methods by exporting a function with the name of the method in the paths `route.ts` file.
32
-
33
- For example:
34
-
35
- ```ts
36
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
37
-
38
- export async function GET(req: MedusaRequest, res: MedusaResponse) {
39
- // Handle GET requests
40
- }
41
-
42
- export async function POST(req: MedusaRequest, res: MedusaResponse) {
43
- // Handle POST requests
44
- }
45
-
46
- export async function PUT(req: MedusaRequest, res: MedusaResponse) {
47
- // Handle PUT requests
48
- }
49
- ```
50
-
51
- ## Parameters
52
-
53
- To create an API route that accepts a path parameter, create a directory within the route's path whose name is of the format `[param]`.
54
-
55
- For example, if you want to define a route that takes a `productId` parameter, you can do so by creating a file called `/api/products/[productId]/route.ts`:
56
-
57
- ```ts
58
- import type {
59
- MedusaRequest,
60
- MedusaResponse,
61
- } from "@medusajs/framework/http"
62
-
63
- export async function GET(req: MedusaRequest, res: MedusaResponse) {
64
- const { productId } = req.params;
65
-
66
- res.json({
67
- message: `You're looking for product ${productId}`
68
- })
69
- }
70
- ```
71
-
72
- To create an API route that accepts multiple path parameters, create within the file's path multiple directories whose names are of the format `[param]`.
73
-
74
- For example, if you want to define a route that takes both a `productId` and a `variantId` parameter, you can do so by creating a file called `/api/products/[productId]/variants/[variantId]/route.ts`.
75
-
76
- ## Using the container
77
-
78
- The Medusa container is available on `req.scope`. Use it to access modules' main services and other registered resources:
79
-
80
- ```ts
81
- import type {
82
- MedusaRequest,
83
- MedusaResponse,
84
- } from "@medusajs/framework/http"
85
-
86
- export const GET = async (
87
- req: MedusaRequest,
88
- res: MedusaResponse
89
- ) => {
90
- const productModuleService = req.scope.resolve("product")
91
-
92
- const [, count] = await productModuleService.listAndCount()
93
-
94
- res.json({
95
- count,
96
- })
97
- }
98
- ```
99
-
100
- ## Middleware
101
-
102
- You can apply middleware to your routes by creating a file called `/api/middlewares.ts`. This file must export a configuration object with what middleware you want to apply to which routes.
103
-
104
- For example, if you want to apply a custom middleware function to the `/store/custom` route, you can do so by adding the following to your `/api/middlewares.ts` file:
105
-
106
- ```ts
107
- import { defineMiddlewares } from "@medusajs/framework/http"
108
- import type {
109
- MedusaRequest,
110
- MedusaResponse,
111
- MedusaNextFunction,
112
- } from "@medusajs/framework/http";
113
-
114
- async function logger(
115
- req: MedusaRequest,
116
- res: MedusaResponse,
117
- next: MedusaNextFunction
118
- ) {
119
- console.log("Request received");
120
- next();
121
- }
122
-
123
- export default defineMiddlewares({
124
- routes: [
125
- {
126
- matcher: "/store/custom",
127
- middlewares: [logger],
128
- },
129
- ],
130
- })
131
- ```
132
-
133
- The `matcher` property can be either a string or a regular expression. The `middlewares` property accepts an array of middleware functions.
@@ -1,8 +0,0 @@
1
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
2
-
3
- export async function GET(
4
- req: MedusaRequest,
5
- res: MedusaResponse
6
- ) {
7
- res.sendStatus(200);
8
- }
@@ -1,81 +0,0 @@
1
- import type { CreateCustomerDTO } from "@medusajs/framework/types"
2
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
3
- import { Modules } from "@medusajs/framework/utils"
4
-
5
- /**
6
- * Override the store customer registration route
7
- * This route handles POST /store/customers
8
- * Based on: https://docs.medusajs.com/resources/storefront-development/customers/register
9
- *
10
- * Forces email_verified = false and phone_verified = false on all new customer registrations
11
- */
12
-
13
-
14
- type CreateCustomerExtendedDTO = Partial<CreateCustomerDTO> & {
15
- email_verified?: boolean
16
- phone_verified?: boolean
17
- }
18
-
19
-
20
- export async function POST(
21
- req: MedusaRequest,
22
- res: MedusaResponse
23
- ) {
24
- const customerModuleService = req.scope.resolve(Modules.CUSTOMER)
25
- const body = req.body as CreateCustomerExtendedDTO
26
-
27
- try {
28
- // Extract customer data from request body
29
- const {
30
- email,
31
- first_name,
32
- last_name,
33
- phone,
34
- company_name,
35
- metadata,
36
- email_verified: _incomingEmailVerified,
37
- phone_verified: _incomingPhoneVerified,
38
- ...rest
39
- } = body
40
-
41
- // Prepare customer data with verified flags forced to false
42
- const customerData: CreateCustomerExtendedDTO = {
43
- ...rest,
44
- email,
45
- first_name,
46
- last_name,
47
- phone,
48
- company_name,
49
- metadata: metadata ?? {},
50
- email_verified: false,
51
- phone_verified: false,
52
- }
53
-
54
- // Create the customer using the customer module service
55
- // createCustomers expects an array
56
- const customers = await customerModuleService.createCustomers([customerData])
57
- const customer = customers[0]
58
-
59
- // Retrieve the created customer to return
60
- const createdCustomer = await customerModuleService.retrieveCustomer(customer.id)
61
-
62
- // Return the customer in the expected format
63
- res.status(201).json({
64
- customer: createdCustomer,
65
- })
66
- } catch (error: any) {
67
- // Handle errors appropriately
68
- if (error.message?.includes("already exists") || error.message?.includes("duplicate")) {
69
- res.status(409).json({
70
- message: "A customer with this email already exists",
71
- type: "duplicate_error",
72
- })
73
- } else {
74
- res.status(400).json({
75
- message: error.message || "Failed to create customer",
76
- type: "invalid_data",
77
- })
78
- }
79
- }
80
- }
81
-
@@ -1,8 +0,0 @@
1
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
2
-
3
- export async function GET(
4
- req: MedusaRequest,
5
- res: MedusaResponse
6
- ) {
7
- res.sendStatus(200);
8
- }
@@ -1,36 +0,0 @@
1
- # Custom scheduled jobs
2
-
3
- A scheduled job is a function executed at a specified interval of time in the background of your Medusa application.
4
-
5
- A scheduled job is created in a TypeScript or JavaScript file under the `src/jobs` directory.
6
-
7
- For example, create the file `src/jobs/hello-world.ts` with the following content:
8
-
9
- ```ts
10
- import {
11
- MedusaContainer
12
- } from "@medusajs/framework/types";
13
-
14
- export default async function myCustomJob(container: MedusaContainer) {
15
- const productService = container.resolve("product")
16
-
17
- const products = await productService.listAndCountProducts();
18
-
19
- // Do something with the products
20
- }
21
-
22
- export const config = {
23
- name: "daily-product-report",
24
- schedule: "0 0 * * *", // Every day at midnight
25
- };
26
- ```
27
-
28
- A scheduled job file must export:
29
-
30
- - The function to be executed whenever it’s time to run the scheduled job.
31
- - A configuration object defining the job. It has three properties:
32
- - `name`: a unique name for the job.
33
- - `schedule`: a [cron expression](https://crontab.guru/).
34
- - `numberOfExecutions`: an optional integer, specifying how many times the job will execute before being removed
35
-
36
- The `handler` is a function that accepts one parameter, `container`, which is a `MedusaContainer` instance used to resolve services.
@@ -1,26 +0,0 @@
1
- # Module Links
2
-
3
- A module link forms an association between two data models of different modules, while maintaining module isolation.
4
-
5
- Learn more about links in [this documentation](https://docs.medusajs.com/learn/fundamentals/module-links)
6
-
7
- For example:
8
-
9
- ```ts
10
- import BlogModule from "../modules/blog"
11
- import ProductModule from "@medusajs/medusa/product"
12
- import { defineLink } from "@medusajs/framework/utils"
13
-
14
- export default defineLink(
15
- ProductModule.linkable.product,
16
- BlogModule.linkable.post
17
- )
18
- ```
19
-
20
- This defines a link between the Product Module's `product` data model and the Blog Module (custom module)'s `post` data model.
21
-
22
- Then, in the Medusa application using this plugin, run the following command to sync the links to the database:
23
-
24
- ```bash
25
- npx medusa db:migrate
26
- ```
@@ -1,24 +0,0 @@
1
- import { Migration } from "@medusajs/framework/mikro-orm/migrations"
2
-
3
- const TABLE_NAME = "customer"
4
- const EMAIL_COLUMN = "email_verified"
5
- const PHONE_COLUMN = "phone_verified"
6
-
7
- export class Migration20250118000000 extends Migration {
8
- override async up(): Promise<void> {
9
- this.addSql(
10
- `alter table "${TABLE_NAME}"
11
- add column if not exists "${EMAIL_COLUMN}" boolean not null default false,
12
- add column if not exists "${PHONE_COLUMN}" boolean not null default false;`
13
- )
14
- }
15
-
16
- override async down(): Promise<void> {
17
- this.addSql(
18
- `alter table "${TABLE_NAME}"
19
- drop column if exists "${EMAIL_COLUMN}",
20
- drop column if exists "${PHONE_COLUMN}";`
21
- )
22
- }
23
- }
24
-
@@ -1,116 +0,0 @@
1
- # Custom Module
2
-
3
- A module is a package of reusable functionalities. It can be integrated into your Medusa application without affecting the overall system. You can create a module as part of a plugin.
4
-
5
- Learn more about modules in [this documentation](https://docs.medusajs.com/learn/fundamentals/modules).
6
-
7
- To create a module:
8
-
9
- ## 1. Create a Data Model
10
-
11
- A data model represents a table in the database. You create a data model in a TypeScript or JavaScript file under the `models` directory of a module.
12
-
13
- For example, create the file `src/modules/blog/models/post.ts` with the following content:
14
-
15
- ```ts
16
- import { model } from "@medusajs/framework/utils"
17
-
18
- const Post = model.define("post", {
19
- id: model.id().primaryKey(),
20
- title: model.text(),
21
- })
22
-
23
- export default Post
24
- ```
25
-
26
- ## 2. Create a Service
27
-
28
- A module must define a service. A service is a TypeScript or JavaScript class holding methods related to a business logic or commerce functionality.
29
-
30
- For example, create the file `src/modules/blog/service.ts` with the following content:
31
-
32
- ```ts
33
- import { MedusaService } from "@medusajs/framework/utils"
34
- import Post from "./models/post"
35
-
36
- class BlogModuleService extends MedusaService({
37
- Post,
38
- }){
39
- }
40
-
41
- export default BlogModuleService
42
- ```
43
-
44
- ## 3. Export Module Definition
45
-
46
- A module must have an `index.ts` file in its root directory that exports its definition. The definition specifies the main service of the module.
47
-
48
- For example, create the file `src/modules/blog/index.ts` with the following content:
49
-
50
- ```ts
51
- import BlogModuleService from "./service"
52
- import { Module } from "@medusajs/framework/utils"
53
-
54
- export const BLOG_MODULE = "blog"
55
-
56
- export default Module(BLOG_MODULE, {
57
- service: BlogModuleService,
58
- })
59
- ```
60
-
61
- ## 4. Generate Migrations
62
-
63
- To generate migrations for your module, run the following command in the plugin's directory:
64
-
65
- ```bash
66
- npx medusa plugin:db:genreate
67
- ```
68
-
69
- ## Use Module
70
-
71
- You can use the module in customizations within the plugin or within the Medusa application using this plugin. When the plugin is added to a Medusa application, all its modules are registered as well.
72
-
73
- For example, to use the module in an API route:
74
-
75
- ```ts
76
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework"
77
- import BlogModuleService from "../../../modules/blog/service"
78
- import { BLOG_MODULE } from "../../../modules/blog"
79
-
80
- export async function GET(
81
- req: MedusaRequest,
82
- res: MedusaResponse
83
- ): Promise<void> {
84
- const blogModuleService: BlogModuleService = req.scope.resolve(
85
- BLOG_MODULE
86
- )
87
-
88
- const posts = await blogModuleService.listPosts()
89
-
90
- res.json({
91
- posts
92
- })
93
- }
94
- ```
95
-
96
- ## Module Options
97
-
98
- When you register the plugin in the Medusa application, it can accept options. These options are passed to the modules within the plugin:
99
-
100
- ```ts
101
- import { defineConfig } from "@medusajs/framework/utils"
102
-
103
- module.exports = defineConfig({
104
- // ...
105
- plugins: [
106
- {
107
- resolve: "@myorg/plugin-name",
108
- options: {
109
- apiKey: process.env.API_KEY,
110
- },
111
- },
112
- ],
113
- })
114
- ```
115
-
116
- Learn more about module options in [this documentation](https://docs.medusajs.com/learn/fundamentals/modules/options).
@@ -1,30 +0,0 @@
1
- ## Module Providers
2
-
3
- You can create module providers, such as Notification or File Module Providers under a sub-directory of this directory. For example, `src/providers/my-notification`.
4
-
5
- Then, you register them in the Medusa application as `plugin-name/providers/my-notification`:
6
-
7
- ```ts
8
- module.exports = defineConfig({
9
- // ...
10
- modules: [
11
- {
12
- resolve: "@medusajs/medusa/notification",
13
- options: {
14
- providers: [
15
- {
16
- resolve: "@myorg/plugin-name/providers/my-notification",
17
- id: "my-notification",
18
- options: {
19
- channels: ["email"],
20
- // provider options...
21
- },
22
- },
23
- ],
24
- },
25
- },
26
- ],
27
- })
28
- ```
29
-
30
- Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/plugins/create).
@@ -1,59 +0,0 @@
1
- # Custom subscribers
2
-
3
- Subscribers handle events emitted in the Medusa application.
4
-
5
- The subscriber is created in a TypeScript or JavaScript file under the `src/subscribers` directory.
6
-
7
- For example, create the file `src/subscribers/product-created.ts` with the following content:
8
-
9
- ```ts
10
- import {
11
- type SubscriberConfig,
12
- } from "@medusajs/framework"
13
-
14
- // subscriber function
15
- export default async function productCreateHandler() {
16
- console.log("A product was created")
17
- }
18
-
19
- // subscriber config
20
- export const config: SubscriberConfig = {
21
- event: "product.created",
22
- }
23
- ```
24
-
25
- A subscriber file must export:
26
-
27
- - The subscriber function that is an asynchronous function executed whenever the associated event is triggered.
28
- - A configuration object defining the event this subscriber is listening to.
29
-
30
- ## Subscriber Parameters
31
-
32
- A subscriber receives an object having the following properties:
33
-
34
- - `event`: An object holding the event's details. It has a `data` property, which is the event's data payload.
35
- - `container`: The Medusa container. Use it to resolve modules' main services and other registered resources.
36
-
37
- ```ts
38
- import type {
39
- SubscriberArgs,
40
- SubscriberConfig,
41
- } from "@medusajs/framework"
42
-
43
- export default async function productCreateHandler({
44
- event: { data },
45
- container,
46
- }: SubscriberArgs<{ id: string }>) {
47
- const productId = data.id
48
-
49
- const productModuleService = container.resolve("product")
50
-
51
- const product = await productModuleService.retrieveProduct(productId)
52
-
53
- console.log(`The product ${product.title} was created`)
54
- }
55
-
56
- export const config: SubscriberConfig = {
57
- event: "product.created",
58
- }
59
- ```
@@ -1,93 +0,0 @@
1
- import type { Logger } from "@medusajs/framework/types"
2
- import type {
3
- SubscriberArgs,
4
- SubscriberConfig,
5
- } from "@medusajs/framework/subscribers"
6
- import {
7
- ContainerRegistrationKeys,
8
- CustomerWorkflowEvents,
9
- Modules,
10
- } from "@medusajs/framework/utils"
11
-
12
- type CustomerCreatedPayload = Array<{
13
- id: string
14
- }>
15
-
16
- const WELCOME_TEMPLATE_ID =
17
- process.env.CUSTOMER_REGISTRATION_WELCOME_TEMPLATE_ID ||
18
- "customer-registration-success"
19
-
20
- export default async function handleCustomerCreated({
21
- event: { data },
22
- container,
23
- }: SubscriberArgs<CustomerCreatedPayload>) {
24
- const logger: Logger | null = container.hasRegistration(
25
- ContainerRegistrationKeys.LOGGER
26
- )
27
- ? container.resolve(ContainerRegistrationKeys.LOGGER)
28
- : null
29
-
30
- if (!Array.isArray(data) || !data.length) {
31
- return
32
- }
33
-
34
- const customerIds = data
35
- .map((payload) => payload?.id)
36
- .filter((id): id is string => typeof id === "string" && !!id)
37
-
38
- if (!customerIds.length) {
39
- return
40
- }
41
-
42
- try {
43
- const customerModuleService = container.resolve(Modules.CUSTOMER)
44
-
45
- const customers = await customerModuleService.listCustomers({
46
- id: customerIds,
47
- })
48
-
49
- if (!customers.length) {
50
- return
51
- }
52
-
53
- if (!container.hasRegistration(Modules.NOTIFICATION)) {
54
- logger?.warn?.(
55
- "[customer-registration] Notification module is not configured. Skipping welcome email."
56
- )
57
- return
58
- }
59
-
60
- const notificationModuleService = container.resolve(Modules.NOTIFICATION)
61
-
62
- await notificationModuleService.createNotifications(
63
- customers.map((customer) => ({
64
- to: customer.email,
65
- channel: "email",
66
- template: WELCOME_TEMPLATE_ID,
67
- resource_id: customer.id,
68
- resource_type: "customer",
69
- receiver_id: customer.id,
70
- trigger_type: "customer-registration",
71
- data: {
72
- email: customer.email,
73
- first_name: customer.first_name,
74
- last_name: customer.last_name,
75
- full_name: [customer.first_name, customer.last_name]
76
- .filter(Boolean)
77
- .join(" ")
78
- .trim(),
79
- },
80
- }))
81
- )
82
- } catch (error) {
83
- logger?.error?.(
84
- "[customer-registration] Failed to send welcome email",
85
- error
86
- )
87
- }
88
- }
89
-
90
- export const config: SubscriberConfig = {
91
- event: CustomerWorkflowEvents.CREATED,
92
- }
93
-
@@ -1,79 +0,0 @@
1
- # Custom Workflows
2
-
3
- A workflow is a series of queries and actions that complete a task.
4
-
5
- The workflow is created in a TypeScript or JavaScript file under the `src/workflows` directory.
6
-
7
- For example:
8
-
9
- ```ts
10
- import {
11
- createStep,
12
- createWorkflow,
13
- WorkflowResponse,
14
- StepResponse,
15
- } from "@medusajs/framework/workflows-sdk"
16
-
17
- const step1 = createStep("step-1", async () => {
18
- return new StepResponse(`Hello from step one!`)
19
- })
20
-
21
- type WorkflowInput = {
22
- name: string
23
- }
24
-
25
- const step2 = createStep(
26
- "step-2",
27
- async ({ name }: WorkflowInput) => {
28
- return new StepResponse(`Hello ${name} from step two!`)
29
- }
30
- )
31
-
32
- type WorkflowOutput = {
33
- message1: string
34
- message2: string
35
- }
36
-
37
- const helloWorldWorkflow = createWorkflow(
38
- "hello-world",
39
- (input: WorkflowInput) => {
40
- const greeting1 = step1()
41
- const greeting2 = step2(input)
42
-
43
- return new WorkflowResponse({
44
- message1: greeting1,
45
- message2: greeting2
46
- })
47
- }
48
- )
49
-
50
- export default helloWorldWorkflow
51
- ```
52
-
53
- ## Execute Workflow
54
-
55
- You can execute the workflow from other resources, such as API routes, scheduled jobs, or subscribers.
56
-
57
- For example, to execute the workflow in an API route:
58
-
59
- ```ts
60
- import type {
61
- MedusaRequest,
62
- MedusaResponse,
63
- } from "@medusajs/framework"
64
- import myWorkflow from "../../../workflows/hello-world"
65
-
66
- export async function GET(
67
- req: MedusaRequest,
68
- res: MedusaResponse
69
- ) {
70
- const { result } = await myWorkflow(req.scope)
71
- .run({
72
- input: {
73
- name: req.query.name as string,
74
- },
75
- })
76
-
77
- res.send(result)
78
- }
79
- ```