• Сообщество
  • Блог
Документация
Плагины и интеграцииВсе расширения для Medusa от сообществаСтартерыЗапускайте проекты быстрее с готовыми решениями
ЭкспертыПодберите специалиста для разработки и развития вашего проекта на MedusaКейсыПосмотрите примеры Medusa в продакшене и успешные внедрения
Меч Moscow
Комплексная e-commerce платформа на Medusa для московского fashion-бренда

Меч Moscow · Fashion

Gorgo снижает затраты на адаптацию Medusa к локальным рынкам.

Мы разрабатываем плагины интеграции, осуществляем поддержку и развиваем сообщество разработчиков на Medusa в Telegram.

  • Ресурсы Medusa
  • Плагины и интеграции
  • Стартеры
  • Эксперты
  • Кейсы
  • Medusa Чат в Telegram
  • Medusa Новости в Telegram
  • Документация Gorgo
  • Связаться с нами
  • TelegramGitHub
Плагины
W

Webbers medusa

Quality-of-life admin customizations for Medusa v2 (notes, customer anonymization, guest order transfer, price-list discount control)

npm install @webbers/webbers-medusa
Категория
Другое
Создано
Webbers
Версия
1.0.0
Последнее обновление
4 дня назад
Ежемесячные загрузки
Загрузка данных
Звезды на Github
0
npmNPMGitHubGithub

@webbers/webbers-medusa

Quality-of-life admin customizations for Medusa v2, extracted from the Frecious backend so they can be reused across projects.

Features

  • Notes — free-text notes on any entity, shown in the admin detail sidebar. Ships customer and order notes (with links + widgets); the generic module, endpoint and note component are reusable so other plugins can attach notes to their own entities (see "Attaching notes to your own entities").
  • Anonymize customer — AVG/GDPR data scrub for a customer across customer, cart and order records, plus any host-configured extra tables (superadmin only). See "Configuring extra tables".
  • Transfer guest orders — merge unassigned guest orders (matched by email) into a customer account.
  • Edit customer email — change a registered customer's email atomically across customer and auth identity, plus any host-configured extra tables (clears the cached Klaviyo id). See "Configuring extra tables".
  • Order discount breakdown — sidebar widget summarising the discount codes applied to an order, with amounts and percentages.
  • Order notifications — sidebar widget listing the transactional emails sent for an order.
  • Always-free promotion — flag a promotion as "always free shipping". Ships the data model, workflows, link and admin widget; the host app wires the trigger (see below).
  • Disallow price-list discounts — a toggle on the price-list detail page that stops discount/promotion codes from overriding that price list. Ships the data model, link, workflows, admin API + widget, and a store add-to-cart endpoint that enforces it; the storefront wires the endpoint (see below).

Toggling features

The plugin adds a Settings → Webbers QoL page in the admin with a switch per feature. Toggling a feature off:

  • hides its admin widget — every widget reads the flags via and renders nothing when disabled; and
  • blocks its endpoints — a middleware returns for the feature's API routes (see ).

Flags are persisted by the bundled module (table ) and default to ON when no row exists, so the plugin is fully active out of the box. The canonical list of features lives in (shared by the server and admin builds).

Always-free is widget-only gating: disabling it hides the promotion switch, but the actual link is still written by the host app's promotion hooks, so leave the hooks in place if you rely on existing always-free promotions.

After installing, run the migration so the flag table exists:

npx medusa db:migrate

What's included

ConcernLocation
Feature catalog (single source of truth)
Settings module + migration (feature flags)
Settings admin page (Settings → Webbers QoL)
Note module + migration
Always-free () module + migration
Price-list-ext () module + migration
Links (customer/order note, promotion always-free, price-list-ext)
Workflows (note, always-free, price-list-ext)
Admin widgets
Admin API routes
Store API route (price-list-aware add-to-cart)
HTTP middlewares (note + edit-email validators, promotion , price-list-ext)

Installation

Install the plugin in your Medusa app:

1npm install @webbers/webbers-medusa
2# or, during local development:
3npx medusa plugin:add @webbers/webbers-medusa

Register it in the host app's :

1plugins: [
2 {
3 resolve: '@webbers/webbers-medusa',
4 options: {},
5 },
6]

After installing, run the migrations so the plugin's tables exist:

npx medusa db:migrate

Runtime dependencies in the host app

  • A module exposing / — the anonymize, transfer-guest-orders, edit-email and order-notifications routes resolve it from the container by key (). It is intentionally not bundled here so the host app keeps a single shared instance.

This plugin has no dependency on any subscriptions package (or any other domain module). The anonymize / edit-email routines only touch , and tables out of the box; any extra tables (e.g. ) are declared by the host via plugin options — see "Configuring extra tables" below.

Configuring extra tables (anonymize / edit-email)

The anonymize and edit-email routines scrub the core Medusa tables on their own. To include tables owned by other modules — without this plugin depending on them — declare them in the plugin options in your . Every table/column name is validated () and all statements run inside the same transaction as the core scrub, so atomicity is preserved.

1plugins: [
2 {
3 resolve: '@webbers/webbers-medusa',
4 options: {
5 anonymize: {
6 // Domain for the generated anonymized email (anon+<ts>@<domain>). Default: anonymized.invalid
7 emailDomain: 'example.com',
8 // Tables with a customer-id column: set email columns to the anon email, null the rest.
9 customerScopedTables: [
10 { table: 'subscription', emailColumns: ['email'], nullColumns: ['payment_config'] },
11 ],
12 // Address tables reached via a parent table's billing/shipping FKs (nulled out).
13 // customerIdColumn/billingColumn/shippingColumn/nullColumns are optional (sensible defaults).
14 addressTables: [
15 { addressTable: 'subscription_address', parentTable: 'subscription' },
16 ],
17 },
18 editEmail: {
19 // Tables whose email column should follow the customer's new email.
20 // emailColumn defaults to 'email', customerIdColumn to 'customer_id'.
21 emailSyncTables: [{ table: 'subscription' }],
22 },
23 },
24 },
25]

Attaching notes to your own entities

The module, the endpoint ( / are free-form strings) and the note component are generic. To add notes to another entity from a separate plugin or your app — e.g. subscriptions — depend on and:

  1. Define the link between your module and the note module:

    1import { defineLink } from '@medusajs/framework/utils';
    2import MyModule from '../modules/my-module';
    3import NoteModule from '@webbers/webbers-medusa/modules/note';
    4
    5export default defineLink(MyModule.linkable.myEntity, NoteModule.linkable.note);
  2. Render a note widget in your entity's detail zone that calls with your (the module's registration key). The feature flag guards the endpoint, so the note stays hidden/blocked when notes are disabled in Settings → Webbers QoL.

This keeps the QoL plugin free of any dependency on your module while still owning the note storage.

Activating the always-free promotion behaviour

Toggling the widget writes on the promotion (the plugin's middleware whitelists that field). Turning that flag into an link requires a handler on Medusa's promotion workflow hooks. Medusa allows only one handler per workflow hook, and a real app usually does other work in / too — so the plugin does not register these hooks for you. Add them in your app's :

1import { createPromotionsWorkflow } from '@medusajs/medusa/core-flows';
2import {
3 createAlwaysFreeFromPromotionWorkflow,
4 CreateAlwaysFreeFromPromotionWorkflowInput,
5} from '@webbers/webbers-medusa/workflows';
6
7createPromotionsWorkflow.hooks.promotionsCreated(async ({ promotions, additional_data }, { container }) => {
8 const workflow = createAlwaysFreeFromPromotionWorkflow(container);
9
10 for (const promotion of promotions) {
11 // ...any other app-specific work you already do here...
12 await workflow.run({
13 input: { promotion, additional_data } as CreateAlwaysFreeFromPromotionWorkflowInput,
14 });
15 }
16});

1import { updatePromotionsWorkflow } from '@medusajs/medusa/core-flows';
2import { ContainerRegistrationKeys } from '@medusajs/framework/utils';
3import {
4 createAlwaysFreeFromPromotionWorkflow,
5 CreateAlwaysFreeFromPromotionWorkflowInput,
6 updateAlwaysFreeFromPromotionWorkflow,
7 UpdateAlwaysFreeFromPromotionStepInput,
8} from '@webbers/webbers-medusa/workflows';
9import PromotionAlwaysFreeLink from '@webbers/webbers-medusa/links/promotion-always-free';
10
11updatePromotionsWorkflow.hooks.promotionsUpdated(async ({ promotions, additional_data }, { container }) => {
12 const query = container.resolve(ContainerRegistrationKeys.QUERY);
13 const create = createAlwaysFreeFromPromotionWorkflow(container);
14 const update = updateAlwaysFreeFromPromotionWorkflow(container);
15
16 for (const promotion of promotions) {
17 const existing = await query.graph({
18 entity: PromotionAlwaysFreeLink.entryPoint,
19 fields: ['*'],
20 filters: { promotion_id: promotion.id },
21 });
22
23 if (!existing?.data?.length) {
24 await create.run({ input: { promotion, additional_data } as CreateAlwaysFreeFromPromotionWorkflowInput });
25 } else {
26 await update.run({ input: { promotion, additional_data } as UpdateAlwaysFreeFromPromotionStepInput });
27 }
28 }
29});

The plugin emits no (, to avoid a type-portability error), so these imports resolve to in the host app unless it sets . That is intentional — keep the casts above so the hook bodies stay type-checked.

Reading the flag elsewhere (e.g. a fulfillment provider deciding shipping is free) can join the link table directly:

1SELECT af.always_free
2FROM promotion p
3LEFT JOIN promotion_promotion_promotion_addition_always_free paf ON paf.promotion_id = p.id
4LEFT JOIN always_free af ON af.id = paf.always_free_id
5WHERE p.id = ?

Activating the disallow-price-list-discounts behaviour

The admin widget writes onto the price list's extended record (, linked to Medusa's ). The toggle only records intent — the enforcement happens when items are added to the cart.

The plugin ships the enforcement endpoint at . It behaves like Medusa's built-in add-to-cart, but when the body's points at a price list flagged , it sets on the line item (and stamps ). When the Disallow price-list discounts feature is toggled off, the endpoint skips that logic and behaves as a plain add-to-cart, so it is safe to call unconditionally.

To activate the behaviour, have the storefront add price-list items through this endpoint instead of the default :

1await sdk.client.fetch(`/store/carts/${cartId}/line-item-price-list`, {
2 method: 'POST',
3 body: {
4 variant_id: variantId,
5 quantity,
6 metadata: { price_list_id: priceListId },
7 },
8});

Build

pnpm --filter @webbers/webbers-medusa build

Еще в этой категории

Посмотреть все
Другое
Gati logo

Gati

От Devx Commerce

Синхронизируйте Medusa с Gati ERP

Загрузка данных
npm
Другое
Product Reviews logo

Product Reviews

От Lambda Curry

Добавляйте рейтинги, отзывы и модерацию товаров

Загрузка данных
GitHubnpm
Другое
Variant Images logo

Variant Images

От Betanoir

Организуйте и загружайте варианты изображений в Medusa

Загрузка данных
GitHubnpm

Еще от этого автора

Посмотреть все
Платежи
Pay. logo

Pay.

От Webbers

Принимайте кредитные карты, цифровые платежи и купи сейчас — плати потом

Загрузка данных
GitHubnpm
Уведомления
Mailgun logo

Mailgun

От Webbers

Отправляйте и управляйте уведомлениями по электронной почте

Загрузка данных
GitHubnpm