Medusa v2 Plugin Development
Before writing code
Fetch live docs:
- Fetch
https://docs.medusajs.com/learn/fundamentals/pluginsfor plugin overview - Web-search
site:docs.medusajs.com plugin vs modulefor comparison - Web-search
site:docs.medusajs.com create medusa pluginfor plugin scaffolding - Web-search
site:docs.medusajs.com plugin options configurationfor plugin options pattern - Web-search
site:docs.medusajs.com publish plugin npmfor packaging and distribution
Plugin vs Module
| Aspect | Module | Plugin | |--------|--------|--------| | Scope | Single concern (data + service) | Full feature (modules + routes + workflows + admin) | | Location | src/modules/ in project | Separate npm package | | Distribution | Not distributable | Published to npm | | Configuration | Registered in modules array | Registered in plugins array | | Contains | Models, service, loaders | Modules, API routes, workflows, subscribers, admin, links | | Use case | Project-specific domain | Reusable across projects |
A plugin is essentially a packaged Medusa project that can contain all the same src/ directories.
Plugin Directory Structure
| Directory | Contents | |-----------|----------| | src/modules/ | Custom modules (models, services) | | src/workflows/ | Workflow definitions | | src/api/store/, src/api/admin/ | API routes + middlewares.ts | | src/subscribers/ | Event subscribers | | src/admin/widgets/, src/admin/routes/ | Admin UI extensions | | src/links/ | Module link definitions | | Root | package.json, tsconfig.json, README.md |
A plugin mirrors the standard Medusa src/ directory structure — all conventions apply.
Plugin Definition
The plugin's package.json must include specific fields:
package.json Key Fields
| Field | Value | Purpose | |-------|-------|---------| | name | medusa-plugin-my-feature | npm package name | | main | ./dist/index.js | Compiled entry point | | types | ./dist/index.d.ts | TypeScript declarations | | files | ["dist", "!dist/**/*.map"] | Included in npm package | | keywords | ["medusa-plugin"] | Discoverability | | peerDependencies | @medusajs/framework | Medusa version compatibility |
Plugin Entry Point
// src/index.ts
// Fetch live docs for plugin export shape
// Plugin entry exports nothing directly --
// Medusa discovers modules, routes, etc. by convention
export default {}
Plugin Options
Plugins receive configuration options from the consuming project:
Consuming a Plugin
// In consuming project's medusa-config.ts plugins array
// Fetch live docs for plugin registration options
{
resolve: "medusa-plugin-my-feature",
options: { apiKey: process.env.MY_FEATURE_API_KEY },
}
Accessing Options in Plugin Code
// In a plugin loader or service
// Fetch live docs for accessing plugin options from container
// Options are injected via the module/plugin options mechanism
Build and Compilation
| Step | Command | Purpose | |------|---------|---------| | Build | tsc or tsup | Compile TypeScript to dist/ | | Watch | tsc --watch | Development rebuild on changes | | Clean | rm -rf dist | Remove compiled output | | Pack | npm pack | Create tarball for testing |
Local Development with npm link
| Step | Command | Context | |------|---------|---------| | 1. Build plugin | npm run build | In plugin directory | | 2. Create link | npm link | In plugin directory | | 3. Link in project | npm link medusa-plugin-my-feature | In Medusa project | | 4. Register | Add to plugins in medusa-config.ts | In Medusa project | | 5. Migrate | npx medusa db:migrate | In Medusa project |
Plugin Testing
| Approach | Description | |----------|-------------| | Unit tests | Test services, workflow steps in isolation | | Integration tests | Use medusa-test-utils to spin up a test Medusa instance | | Local linking | Link plugin into a real Medusa project and test manually | | CI pipeline | Build + unit tests on every push |
Publishing to npm
Pre-publish Checklist
| Check | Why | |-------|-----| | Build succeeds | Consumers need compiled JS | | files in package.json | Only ship dist/, not src/ | | peerDependencies set | Avoid version conflicts | | README with install instructions | Developer experience | | .npmignore or files field | Exclude tests, config, etc. | | License field set | Legal clarity | | Version follows semver | Compatibility signals |
Publishing Commands
npm version patch # or minor / major
npm publish --access public
# Fetch live docs for Medusa plugin registry conventions
Naming Conventions
| Convention | Example | |-----------|---------| | npm package name | medusa-plugin-my-feature | | Module key inside plugin | my-feature | | API route prefix | /store/my-feature, /admin/my-feature | | Admin widget files | src/admin/widgets/my-feature-widget.tsx |
Best Practices
- Follow the
medusa-plugin-*naming convention for npm discoverability - Keep
peerDependencieson@medusajs/frameworkand@medusajs/medusa-- do not bundle them - Ship only compiled output (
dist/) -- never shipsrc/or test files - Include TypeScript declarations for consuming projects to get type safety
- Document all plugin options in your README with required vs optional markers
- Test with
npm linkagainst a real Medusa project before publishing - Use semantic versioning -- breaking changes to options or models require a major bump
Fetch the Medusa plugin documentation for exact project structure, build configuration, and publishing requirements before implementing.