This guide shows the Medusa-native way to add extra fields to built-in resources such as product, customer, or order.
The short version is:
you do not edit Medusa's native module schema directly
you create your own custom module
you link it to the native resource
you expose it through workflows and API routes
you surface it in the admin with a widget or custom page
We follow the exact path we used in this repo to extend Medusa product with a ProductTechSpec model.
The Core Idea
If you come from Prisma, Rails, Laravel, or a typical monolith, your first instinct is often:
"I just want to add some extra columns to product."
That is not how Medusa wants you to think.
Medusa treats its core commerce modules as isolated building blocks. The point is not that they are "untouchable" in some magical sense. The point is that Medusa wants custom business data to live in your module, while the native model stays the canonical commerce entity.
So for a product extension:
product remains the canonical commerce record
your custom model owns your extra domain data
a module link connects the two
In Medusa terms, this is an extension, not an in-place mutation of the native schema.
This matches the repo's Medusa backend guidance in .agents/skills/building-with-medusa/reference/module-links.md, which explicitly calls out links as the correct way to "extend commerce entities".
Why Medusa Works This Way
This design gives you a few important benefits:
Module isolation
Your custom logic stays in your own module instead of being mixed into Medusa internals.
Clear ownership
product owns commerce behavior.
Your extension model owns the custom business meaning, such as:
technical specifications
brands
reviews
compatibility data
ERP sync metadata
Safer upgrades
You are not patching Medusa's internal product schema and hoping upgrades still work.
Reusable extension pattern
Once you understand custom module + link + workflow + route + widget, you can apply the same pattern to customers, orders, carts, price lists, or any other native resource.
When to Use Metadata Instead
Before we build a full linked model, there is one important nuance:
If your extra data is:
small
loosely structured
rarely queried
not business-critical
then metadata may be enough.
For example:
a marketing label
a temporary external reference
a simple boolean feature flag
But if the data is:
structured
validated
large
edited by staff
shown in custom UI
important for business logic
then a proper linked model is the better choice.
Technical specifications are a good example of data that should usually be a linked model, not just product.metadata.
What We Built
In this repo, we extended native Medusa products with a new model:
ProductTechSpec
It stores structured technical and engineering information such as:
Strictly speaking, the link itself is the real association.
But keeping product_id in the model is still practical when your custom record semantically "belongs to" the product and you want easy lookup or uniqueness at your own module layer.
This is also consistent with the repo's Medusa link guidance.
Step 2: Add the Service and Module Export
Medusa modules need a service and an index export.
Without that global registration step, your route file exists, but Medusa will not apply the auth and body validation middleware to /admin/products/:id/tech-spec.
Step 8: Add the Admin Widget
This is the part people often think is the extension mechanism.
It is not.
The widget is only the UI layer.
The real extension already happened in the backend through:
custom module
link
workflow
route
The widget simply makes the linked data editable inside the Medusa admin.
and the catalog seed now creates linked tech specs after product creation.
That gives you believable examples for:
standby generators
prime generators
ATS products
controllers
monitoring modules
accessories
service kits
spare parts
This makes the guide easier to follow because the resulting UI is not empty.
What This Means Conceptually
At this point the pattern should be clear:
You are not doing this
text
Edit native product schema directly
You are doing this
text
Keep native product canonical
Add ProductTechSpec as custom domain data
Link ProductTechSpec to product
Read/write through workflow + route
Render in admin widget
That is the Medusa extension model.
Full Step-by-Step Summary
If you want the short operational checklist, this is it:
Create a custom model for the data you want to add
Wrap it in a Medusa module and service
Register the module in medusa-config.ts
Define a module link between your custom model and the native Medusa model
Run medusa db:generate <moduleName>
Run medusa db:migrate
Create a workflow for create/update/delete mutations
Add admin or store API routes
Add an admin widget or custom page
Seed sample data so you can verify the result quickly
Common Mistakes
Mistake 1: Trying to add random fields directly to native product
This fights Medusa's architecture.
Use a custom module plus link instead.
Mistake 2: Building only the widget
A widget is not a backend extension.
If the data model and route do not exist, the widget is just UI with nowhere real to store data.
Mistake 3: Forgetting migrations
If you define the model and link but do not run migrations, the relationship does not exist in the database.
Mistake 4: Putting mutation logic directly in the route
Use workflows for mutations.
Mistake 5: Treating metadata like a replacement for a real model
metadata is useful, but it is not a substitute for structured extension data when the shape matters.
When This Pattern Is the Right Choice
Use this pattern when you need to extend native Medusa modules with:
technical specifications
engineering data
ERP attributes
compliance data
certification details
B2B-specific state
compatibility relationships
review or enrichment models
If your extra data deserves its own name and lifecycle, it probably deserves its own module.
Final Takeaway
The correct mental model for Medusa is:
You do not usually customize native commerce models by editing them in place.
You extend them by composing around them.
For products, that means:
native product stays canonical
your custom model stores the extra meaning
a module link joins them
workflows and routes manage them
widgets surface them in admin
That is exactly what we built with ProductTechSpec, and it is the pattern you should reuse whenever you want to extend Medusa without breaking its architecture.