npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

customer-registration

v0.0.134

Published

Medusa plugin that overrides store customer registration, enforces email/phone verification flags, and provides OTP management module.

Readme

customer-registration

Medusa v2 plugin that extends customer auth with OTP verification, phone login (phonepass), verified contact updates, referral linking, and account deletion workflows.

Plugin Overview

This plugin customizes Medusa customer authentication and profile flows with:

  • OTP verification for email/phone with configurable channels (email/SMS)
  • unified register/login routes for email and phone credentials
  • passwordless email OTP login/register (emailotp) alongside email+password
  • passwordless phone OTP login/register (phoneotp) alongside phone+password
  • secure contact change flow (/store/customers/me/contact) with OTP verification
  • account deletion request/cancel flows with OTP and scheduled deletion job
  • referral linkage at signup plus a customer-facing my-referrals endpoint
  • password reset support for both email and phone channels

Problem it solves

It adds production-oriented identity controls that are not available in default customer auth flows:

  • enforce verified contact channels before login
  • allow phone-based auth (phonepass) in Medusa v2
  • avoid direct email/phone mutation without OTP proof
  • formalize account deletion lifecycle with cancellation and delayed execution

Medusa version

Built for Medusa v2 (@medusajs/framework, @medusajs/medusa, @medusajs/cli pinned to 2.11.2).

Installation & Setup

Install package

npm install customer-registration

or

yarn add customer-registration

Register plugin in medusa-config.ts

import { defineConfig, Modules } from "@medusajs/framework/utils"

export default defineConfig({
  modules: [
    {
      resolve: "@medusajs/medusa/auth",
      options: {
        providers: [
          {
            resolve: "customer-registration/providers/phonepass",
            id: "phonepass",
          },
          {
            resolve: "customer-registration/providers/emailotp",
            id: "emailotp",
          },
          {
            resolve: "customer-registration/providers/phoneotp",
            id: "phoneotp",
          },
        ],
      },
    },
  ],
  plugins: [
    {
      resolve: "customer-registration",
      options: {
        storefrontUrl: "http://localhost:8000",
        auth: {
          email: { methods: ["password", "otp"] },
          phone: { methods: ["password", "otp"] },
        },
        registration: { identifier: "both", require_verification: true },
        login: { identifier: "both" },
        email_auth: {
          channel: "email",
          template: "src/templates/emails/otp-verification-default.html",
          subject: "Your login code",
          resendThrottleSeconds: 90,
        },
        phone_auth: { channel: "sms", resendThrottleSeconds: 90 },
        email_verification: { channel: "email", subject: "Verify your email" },
        phone_verification: { channel: "sms" },
        password_reset: {
          template: "src/templates/emails/reset-password.html",
          subject: "Reset Your Password",
          sms_body: "Reset password: {{reset_url}}",
        },
        account_deletion_request: {
          template: "src/templates/emails/account-deletion-request.html",
          subject: "Confirm your account deletion request",
          scheduled_days: 7,
        },
        account_deletion_cancel: {
          template: "src/templates/emails/account-deletion-cancel.html",
          subject: "Confirm cancellation of account deletion",
        },
      },
    },
  ],
})

Run migrations

npx medusa db:migrate

Configuration

Plugin options (options)

| Option | Type | Required | Default | Description | |---|---|---:|---|---| | storefrontUrl | string | No | undefined | Base storefront URL used for password reset link generation. Required when password_reset is configured. | | registration.identifier | "email" \| "phone" \| "both" | No | "email" | Which identifier is used at registration. | | registration.require_verification | boolean | No | true | Whether login requires verification flags (password login only; OTP login proves ownership). | | auth.email.methods | Array<"password" \| "otp"> | No | ["password"] | Storefront auth methods. Add "otp" to enable POST /store/customers/otp/send with type: "email_auth". | | auth.phone.methods | Array<"password" \| "otp"> | No | ["password"] | Storefront auth methods. Add "otp" to enable POST /store/customers/otp/send with type: "phone_auth". | | login.identifier | "email" \| "phone" \| "both" | No | registration.identifier | Which login method(s) are accepted at /auth/customer/emailpass. | | email_auth.channel | string | Yes when OTP enabled | none | Notification channel for unified email OTP login/register. | | email_auth.template | string \| null | No | plugin default | HTML template for login code emails. | | email_auth.subject | string | No | "Your login code" | Subject for login code emails. | | email_auth.resendThrottleSeconds | number | No | 90 | Resend throttle for email OTP auth. | | phone_auth.channel | string | Yes when phone OTP enabled | none | Notification channel for unified phone OTP login/register (typically sms). | | phone_auth.template | string \| null | No | plugin default | SMS template for login codes. | | phone_auth.resendThrottleSeconds | number | No | 90 | Resend throttle for phone OTP auth. | | password_reset.template | string | Conditional | undefined | Email HTML template path for reset email. Required if login channel includes email. | | password_reset.subject | string | No | "Reset Your Password" | Password reset email subject. | | password_reset.sms_body | string | Conditional | undefined | SMS template body for reset flow; supports {{token}}, {{reset_url}}, {{phone}}. Required if login channel includes phone. | | account_deletion_request.template | string | Yes when section used | none | Template for account deletion request OTP notification. | | account_deletion_request.subject | string | No | "Confirm your account deletion request" | Subject for request OTP notification. | | account_deletion_request.scheduled_days | number | No | 7 | Days from confirmation until deletion is due. | | account_deletion_cancel.template | string | Yes when section used | none | Template for cancellation OTP notification. | | account_deletion_cancel.subject | string | No | "Confirm cancellation of account deletion" | Subject for cancellation OTP notification. |

OTP module options (supported by OTP service)

⚠️ Note: these options are implemented in OtpVerificationService/OtpConfig, but are not fully declared in CustomerRegistrationPluginOptions type. Confirm your expected typing strategy before publishing.

| Option | Type | Required | Default | Description | |---|---|---:|---|---| | otpLength | number | No | 6 | OTP length. | | otpCharset | "numeric" \| "alphanumeric" | No | "numeric" | OTP charset. | | otpExpiryMinutes | number | No | 15 | OTP expiry in minutes. | | maxAttempts | number | No | 5 | Max verify attempts before lockout. | | email_verification.channel | string | Yes for email OTP | none | Notification channel (usually email). | | email_verification.template | string \| null | No | null | Template path. | | email_verification.subject | string | No | provider default | Subject for email. | | email_verification.resendThrottleSeconds | number | No | 90 | Resend throttle. | | email_verification.autoSendOnRegistration | boolean | No | undefined | Auto-send toggle (if used by caller flow). | | phone_verification.channel | string | Yes for phone OTP | none | Notification channel (usually sms). | | phone_verification.template | string \| null | No | null | SMS template path. | | phone_verification.resendThrottleSeconds | number | No | 90 | Resend throttle. | | account_deletion_request.channel | string | Yes for strict validation | fallback email | Channel for request OTP notifications. | | account_deletion_request.template | string \| null | Yes | none | Template path. | | account_deletion_request.subject | string | No | provider default | Subject. | | account_deletion_request.resendThrottleSeconds | number | No | 90 | Resend throttle. | | account_deletion_cancel.channel | string | Yes for strict validation | fallback email | Channel for cancellation OTP notifications. | | account_deletion_cancel.template | string \| null | Yes | none | Template path. | | account_deletion_cancel.subject | string | No | provider default | Subject. | | account_deletion_cancel.resendThrottleSeconds | number | No | 90 | Resend throttle. |

Complete config example

{
  resolve: "customer-registration",
  options: {
    storefrontUrl: "https://store.example.com",
    registration: {
      identifier: "both",
      require_verification: true,
    },
    login: {
      identifier: "both",
    },
    otpLength: 6,
    otpCharset: "numeric",
    otpExpiryMinutes: 15,
    maxAttempts: 5,
    email_verification: {
      channel: "email",
      template: "src/templates/emails/otp-verification.html",
      subject: "Verify your email",
      resendThrottleSeconds: 90,
    },
    phone_verification: {
      channel: "sms",
      template: "src/templates/sms/otp.txt",
      resendThrottleSeconds: 90,
    },
    password_reset: {
      template: "src/templates/emails/reset-password.html",
      subject: "Reset Your Password",
      sms_body: "Reset password: {{reset_url}}",
    },
    account_deletion_request: {
      channel: "email",
      template: "src/templates/emails/account-deletion-request.html",
      subject: "Confirm your account deletion request",
      scheduled_days: 7,
    },
    account_deletion_cancel: {
      channel: "email",
      template: "src/templates/emails/account-deletion-cancel.html",
      subject: "Confirm cancellation",
    },
  },
}

Environment Variables

Only one direct environment variable usage exists in plugin source:

| Variable | Required | Example | Purpose | |---|---:|---|---| | NODE_ENV | No | production | Used for development-only OTP config debug logging in OTP service. |

⚠️ Note: JWT secret/expiry are read from Medusa config module (projectConfig.http.jwtSecret / jwtExpiresIn), not directly from process.env in this plugin.

REST APIs / Routes

Auth routes

POST /auth/customer/emailpass/register

  • Auth: Public
  • Description: Registers customer identity through emailpass or phonepass based on registration.identifier.

Body:

| Field | Type | Required | Notes | |---|---|---:|---| | password | string | Yes | Required always. | | email | string | Conditional | Required when mode resolves to email. | | phone | string | Conditional | Required when mode resolves to phone. |

Response:

{ "token": "..." }
curl -X POST http://localhost:9000/auth/customer/emailpass/register \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"Secret123!"}'

POST /auth/customer/emailpass

  • Auth: Public
  • Description: Unified login endpoint for email/phone credentials.

Body/query params:

| Field | Type | Required | Notes | |---|---|---:|---| | password | string | Yes | Required. | | email | string | XOR | Use with password. | | phone | string | XOR | Use with password. | | email_or_phone | string | Optional alternative | Must not be combined with email/phone; value with @ becomes email. |

Response:

{ "token": "..." }

POST /auth/customer/emailpass/reset-password

  • Auth: Public
  • Description: Requests password reset for email or phone channel depending on config.

Body:

| Field | Type | Required | Notes | |---|---|---:|---| | identifier | string | Conditional | Email alias field. | | email | string | Conditional | Email lookup. | | phone | string | Conditional | Phone lookup. | | email_or_phone | string | Conditional | Auto-resolves by @. |

Response:

{}

POST /auth/customer/emailpass/update

  • Auth: Public with reset token
  • Description: Completes password reset via Authorization: Bearer <token>.

Body:

| Field | Type | Required | |---|---|---:| | password | string | Yes | | identifier or email or phone or email_or_phone | string | Yes (exactly one logical lookup) |

Response:

{ "message": "Password reset successfully", "success": true }

Storefront flows

  • Password (unchanged): POST /auth/customer/emailpass/registerPOST /store/customers → optional POST /store/customers/otp/send + verify with type: "email_verification".
  • Email OTP (unified): POST /store/customers/otp/send (type: "email_auth") → POST /store/customers/otp/verify → customer is logged in immediately (creates account with email only).
  • Phone OTP (unified): POST /store/customers/otp/send (type: "phone_auth") → POST /store/customers/otp/verify → logged in immediately (creates account with phone only).
  • Both methods: Show a toggle such as “Continue with password” vs “Continue with email/SMS code” when auth.email.methods or auth.phone.methods includes "otp".

OTP auth vs password registration: registration.identifier: "both" still requires email and phone on password registration (POST /auth/customer/emailpass/registerPOST /store/customers). OTP login/register is channel-scoped: whichever channel the user verifies (email_auth or phone_auth) is the only identifier required to create the account.

Breaking change: /auth/customer/emailotp/* and /auth/customer/phoneotp/* were removed. Use /store/customers/otp/* with type: "email_auth" or type: "phone_auth" instead.

For dual OTP stores use registration.identifier: "both", login.identifier: "both", auth.email.methods / auth.phone.methods including "otp", plus email_auth and phone_auth channel config.

Cross-channel OTP login: When login.identifier is "both", a customer who registered with password on one channel (e.g. email) and added the other identifier on their profile can OTP-login on that channel without completing verification OTP first. The plugin auto-attaches phoneotp or emailotp to their existing auth identity on first OTP login.

Store routes

POST /store/customers

  • Auth: Pre-customer auth token (registration flow)
  • Description: Overrides default customer creation; supports phone and referral linking.

Body:

| Field | Type | Required | Notes | |---|---|---:|---| | email | string | Conditional | Depends on registration.identifier. | | phone | string | Conditional | Depends on registration.identifier. | | first_name | string | No | | | last_name | string | No | | | company_name | string | No | | | metadata | object | No | metadata.referral_code is consumed then removed. | | referral_code | string | No | Referrer customer id. |

Response:

{ "customer": { "id": "cus_...", "...": "..." } }

GET /store/customers/me

  • Auth: Customer JWT
  • Description: Returns customer with appended email_verified and phone_verified.

PATCH /store/customers/me

  • Auth: Customer JWT
  • Description: Updates non-contact profile fields only.
  • Rejects: email, phone with NOT_ALLOWED.

POST /store/customers/me/contact

  • Auth: Customer JWT
  • Description: Starts contact change OTP flow.

Body:

| Field | Type | Required | Notes | |---|---|---:|---| | email | string | XOR | Exactly one of email/phone. | | phone | string | XOR | Exactly one of email/phone. |

Response:

{ "token": "...", "expires_at": "..." }

POST /store/customers/me/contact/verify

  • Auth: Customer JWT
  • Description: Verifies OTP and atomically updates contact + verification flags.

Body:

| Field | Type | Required | |---|---|---:| | token | string | Yes | | code | string | Yes |

Response:

{ "customer": { "id": "cus_..." }, "token": "..." }

POST /store/customers/otp/send

  • Auth: Public (rate-limited: 5 requests/minute per IP)
  • Description: Unified OTP send for verification and passwordless login/register.

Verification (email_verification / phone_verification):

| Field | Type | Required | |---|---|---:| | customer_id | string | Yes | | type | "email_verification" \| "phone_verification" | Yes |

curl -X POST http://localhost:9000/store/customers/otp/send \
  -H "Content-Type: application/json" \
  -d '{"customer_id":"cus_...","type":"email_verification"}'

Email login/register (email_auth, requires auth.email.methods includes "otp"):

| Field | Type | Required | |---|---|---:| | email | string | Yes | | type | "email_auth" | Yes |

curl -X POST http://localhost:9000/store/customers/otp/send \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","type":"email_auth"}'

Phone login/register (phone_auth, requires auth.phone.methods includes "otp"):

| Field | Type | Required | |---|---|---:| | phone | string | Yes | | type | "phone_auth" | Yes |

Response (verification):

{ "token": "...", "expires_at": "..." }

Response (auth types — adds is_new_user):

{
  "token": "...",
  "expires_at": "2025-06-03T12:00:00.000Z",
  "is_new_user": true
}

POST /store/customers/otp/verify

  • Auth: Public
  • Description: Verifies OTP for verification flows or passwordless login/register (based on token purpose).

Body:

| Field | Type | Required | Notes | |---|---|---:|---| | token | string | Yes | From send response. | | code | string | Yes | OTP from email/SMS. | | first_name | string | No | Auth types only — used when creating a new customer. | | last_name | string | No | Auth types only. | | referral_code | string | No | Auth types only — referrer customer id. |

curl -X POST http://localhost:9000/store/customers/otp/verify \
  -H "Content-Type: application/json" \
  -d '{"token":"OTP_TOKEN","code":"123456","first_name":"Alice"}'

Response (verification):

{
  "verified": true,
  "customer": {},
  "email_verified": true,
  "token": null,
  "needs_login": true
}

Response (auth types):

{
  "verified": true,
  "customer": { "id": "cus_...", "email": "..." },
  "is_new_user": false,
  "token": "...",
  "needs_login": false
}

POST /store/customers/change-password

  • Auth: Customer JWT
  • Description: Changes password using old/new/confirm values.

Body:

| Field | Type | Required | |---|---|---:| | old_password | string | Yes | | new_password | string | Yes | | confirm_password | string | Yes |

Response:

{ "message": "Password changed successfully", "customer_id": "cus_..." }

POST /store/customers/account-deletion/request

  • Auth: Customer JWT
  • Description: Sends OTP for deletion confirmation.

Body:

| Field | Type | Required | |---|---|---:| | reason | string \| null | No |

Response:

{ "token": "...", "expires_at": "..." }

POST /store/customers/account-deletion/confirm

  • Auth: Public
  • Description: Confirms deletion request via OTP; creates confirmed request row.

Body:

| Field | Type | Required | |---|---|---:| | token | string | Yes | | code | string | Yes |

Response:

{
  "request": {
    "id": "adr_...",
    "customer_id": "cus_...",
    "reason": null,
    "deletion_scheduled_at": "...",
    "status": "confirmed"
  }
}

POST /store/customers/account-deletion/cancel-request

  • Auth: Public (IP rate-limited)
  • Description: Sends OTP for canceling an active deletion request.

Body:

| Field | Type | Required | |---|---|---:| | email | string | Conditional | | phone | string | Conditional |

Response:

{ "token": "...", "expires_at": "..." }

POST /store/customers/account-deletion/cancel-confirm

  • Auth: Public
  • Description: Confirms cancellation OTP and marks request canceled.

Body:

| Field | Type | Required | |---|---|---:| | token | string | Yes | | code | string | Yes |

Response:

{
  "cancelled": true,
  "request": {
    "id": "adr_...",
    "customer_id": "cus_...",
    "status": "cancelled",
    "cancelled_at": "..."
  }
}

GET /store/my-referrals

  • Auth: Customer JWT
  • Description: Returns customers referred by the authenticated customer.

Response:

{
  "children": [
    {
      "referral_link_id": "refl_...",
      "referred_at": "...",
      "parent_customers_by_level": { "1": "cus_referrer" },
      "customer": {
        "id": "cus_child",
        "email": "[email protected]",
        "first_name": null,
        "last_name": null,
        "phone": null,
        "created_at": "..."
      }
    }
  ]
}

Admin routes

GET /admin/account-deletion-requests

  • Auth: Admin JWT
  • Description: Lists account deletion requests with filtering/pagination.

Query params:

| Param | Type | Required | Default | |---|---|---:|---| | status | pending \| confirmed \| cancelled \| completed | No | none | | limit | number | No | 20 | | offset | number | No | 0 | | order | created_at \| updated_at \| customer_id | No | created_at | | order_direction | ASC \| DESC | No | DESC |

Response:

{
  "requests": [],
  "count": 0,
  "offset": 0,
  "limit": 20
}

Services

OtpVerificationService

Manages OTP generation, validation, verification state updates, and channel config lookup.

Key methods:

  • generateOtpWithCode(input, jwtSecret)
  • generateOtpForContactChange(input, jwtSecret, newValue, contactType)
  • verifyOtp(input, jwtSecret)
  • decodeContactChangeToken(token, jwtSecret)
  • getCustomerVerificationByCustomerId(context, customerId)
  • updateEmailVerified(context, customerId)
  • updatePhoneVerified(context, customerId)

AccountDeletionRequestService

Manages deletion lifecycle records and admin/job access patterns.

Key methods:

  • createConfirmed(customer_id, deletion_scheduled_at)
  • cancelRequest(customer_id)
  • getActiveByCustomerId(customer_id)
  • hasPendingRequest(customerId)
  • hasActiveRequest(customerId)
  • listForAdmin(selector, listConfig)
  • listDueForDeletion(limit)
  • markCompleted(id)

ReferralLinkModuleService

CRUD service for referral links (inherits generated methods from MedusaService).

PasswordManagementService

Utility service for password hash lookup/verification/update against provider identities.

CustomerRegistrationService

Placeholder module service for registration-related extension points.

Workflows & Steps

Workflows

| Workflow | Input | Output | Purpose | |---|---|---|---| | send-otp | { customer_id, type } | { token, expires_at } | Generic OTP send pipeline (customer lookup, config, token, notification). | | send-contact-change-otp | { customer_id, new_value, contact_type, otp_type } | { token, expires_at } | Sends OTP to new email/phone; token embeds pending value. | | update-contact | { customer_id, new_value, contact_type, login_identifier } | { customer_id, customer } | Updates contact, sets verified flags, syncs provider identity with compensation. | | verify-email | { customer_id } | { customer_id, email_verified, customer } | Marks email verified and returns updated customer. | | verify-phone | { customer_id, login_identifier? } | { customer_id, phone_verified, customer } | Marks phone verified and syncs phonepass entity id when needed. | | change-password | { customer_id, old_password, new_password, confirm_password } | { customer_id, success } | Validates and updates password through auth provider. |

Steps

  • retrieve-customer
  • resolve-channel-config
  • determine-contact-method
  • generate-otp
  • generate-contact-change-otp
  • prepare-template-data
  • load-template
  • send-notification
  • find-customer-by-email
  • update-password
  • sync-phonepass-entity-id

Subscribers / Event Hooks

auth.password_reset subscriber

  • File: src/subscribers/password-reset.ts
  • Event: auth.password_reset
  • Behavior: renders configured password reset template and sends email notification using notification module.

Admin UI / Extensions

Account Deletion Requests page

  • File: src/admin/routes/account-deletion-requests/page.tsx
  • Placement: Admin route labeled Account Deletion Requests (trash icon)
  • Renders:
    • filterable/paginated table
    • status badges (pending, confirmed, cancelled, completed)
    • refresh + load more interactions
  • Data source: GET /admin/account-deletion-requests

Models & Entities

otp_verification

| Field | Type | Nullable | |---|---|---:| | id | text | No | | customer_id | text | No | | purpose | enum | No | | hashed_code | text | No | | expires_at | datetime | No | | attempts | number | No | | verified_at | datetime | Yes |

account_deletion_request

| Field | Type | Nullable | |---|---|---:| | id | text | No | | customer_id | text | No | | reason | text | Yes | | deletion_scheduled_at | datetime | Yes | | status | enum | No | | cancelled_at | datetime | Yes |

referral_link

| Field | Type | Nullable | |---|---|---:| | id | text | No | | customer_id | text | No | | referrer_id | text | No | | parent_customers_by_level | json | No |

Core Medusa relationships

  • customer_id fields link to Medusa customer table by convention.
  • auth linkage is handled through auth_identity.app_metadata.customer_id and provider_identity.

Jobs

process-account-deletions

  • Schedule: hourly (0 * * * *)
  • Flow:
    1. load due confirmed requests
    2. delete auth identities for customer
    3. delete customer
    4. mark request as completed

Use Cases & Examples

  1. Phone-first storefront signup
    Use POST /auth/customer/emailpass/register with phone+password, then POST /store/customers, then OTP send/verify routes.

  2. Secure email/phone change in customer profile
    Use POST /store/customers/me/contact + POST /store/customers/me/contact/verify instead of direct patch.

  3. Regulatory account deletion process
    Use request/confirm/cancel flows under /store/customers/account-deletion/* and rely on scheduled deletion job.

  4. Referral tree tracking for customers
    Pass referral_code on signup and fetch referrals via GET /store/my-referrals.

  5. Channel-aware password reset
    Use /auth/customer/emailpass/reset-password and /auth/customer/emailpass/update with email, phone, or email_or_phone.

Troubleshooting

storefrontUrl is required when password_reset is configured

  • Cause: password_reset enabled without storefrontUrl.
  • Fix: set options.storefrontUrl.

OTP channel configuration not found

  • Cause: missing email_verification.channel, phone_verification.channel, or account deletion channel/template.
  • Fix: add channel config in plugin options.

Phone login is not enabled / Email login is not enabled

  • Cause: credential channel does not match login.identifier.
  • Fix: align request payload with config, or change login.identifier.

Password reset succeeds with empty {} but user gets no message

  • Cause: security behavior intentionally hides account existence and may swallow notification delivery errors.
  • Fix: verify notification module config, template path, and channel provider setup.

Contact change verify returns unauthorized token mismatch

  • Cause: OTP token used by different authenticated customer.
  • Fix: ensure same customer JWT that initiated the contact-change request is used during verify.

Account deletion routes blocked / login blocked

  • Cause: customer has active (pending/confirmed) deletion request; guard middleware blocks most store routes.
  • Fix: complete cancel flow (cancel-request + cancel-confirm) or allow job to complete deletion.

JWT secret is not configured

  • Cause: Medusa HTTP JWT config missing.
  • Fix: configure projectConfig.http.jwtSecret in Medusa app config.

Helper Utilities

The package exports helper functions from customer-registration/helpers:

  • requestPasswordReset(input, options)
  • completePasswordReset(input, options)
  • createRequestPasswordReset(options)
  • createCompletePasswordReset(options)

These support Medusa SDK client mode or direct fetch mode with optional x-publishable-api-key.

Migrations Included

  • Migration20250118001000CreateOtpVerificationTable
  • Migration20250120000000RemoveForgotPasswordFromOtpPurpose
  • Migration20250221000000AddAccountDeletionOtpPurposes
  • Migration20250120000000AddCustomerVerificationColumns
  • Migration20250221000000CreateAccountDeletionRequestTable
  • Migration20250221100000AddCompletedStatusToAccountDeletionRequest
  • Migration20260502100000CreateReferralLinkTable