Medusa v2 Plugin Development
Before writing code
Fetch live docs:
- Fetch
https://docs.medusajs.com/learn/fundamentals/plugins for plugin overview
- Web-search
site:docs.medusajs.com plugin vs module for comparison
- Web-search
site:docs.medusajs.com create medusa plugin for plugin scaffolding
- Web-search
site:docs.medusajs.com plugin options configuration for plugin options pattern
- Web-search
site:docs.medusajs.com publish plugin npm for 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
peerDependencies on @medusajs/framework and @medusajs/medusa -- do not bundle them
- Ship only compiled output (
dist/) -- never ship src/ 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 link against 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.