Medusa is the set of commerce building blocks that developers can use to create their custom use case. Instead of forcing developers to use a standardized solution, leading them to enforce hacky-workarounds, Medusa embraces customization and extendability.
This tutorial gives an example of Medusa's extensibility, showcasing how you can create a marketplace with our building blocks. While the steps in this tutorial may not lead to a fully-fledged marketplace, they will give you an idea of how it can be implemented by building the foundation. You’ll also find other resources at the end of the tutorial that can guide you through the rest of your marketplace development.
Last year, we wrote a blog about how to create a marketplace with Medusa and Medusa Extender. At the time, Medusa did not provide the necessary functionalities to extend its core, which required developers to use Medusa Extender. With the release of v1.8 of Medusa, the marketplace can now be built with Medusa only. This tutorial illustrates how to do that.
The code of this tutorial is available in this GitHub Repository.
What You’ll be Building
By following along the steps in this tutorial, you’ll be implementing the following logic:
- Every time a user is created, a new store associated with that user is created.
- When the user retrieves the store’s details, their store’s details will be retrieved.
- Every time a product is created, it is associated with the store of the logged-in user.
- The user will only be able to retrieve products from their store.
Prerequisites
This article assumes you already have a Medusa backend with v1.8 of Medusa installed. If not, you can learn how to install it here.
Extend the Store Entity
In this section, you’ll extend the Store entity so that you can later add new relations to it. Extending an entity requires extending its repository as well.
You can learn more about extending an entity in our documentation.
Create the file
with the following content:Copy to clipboardsrc/models/store.ts
This imports the
entity from the Medusa core package asCopy to clipboardStore
and creates a newCopy to clipboardMedusaStore
entity that extends it.Copy to clipboardStore
Extend the StoreRepository
Next, create the file
with the following content:Copy to clipboardsrc/repositories/store.ts
This imports the
from the Medusa core package asCopy to clipboardStoreRepository
, then extends the repository to target the newCopy to clipboardMedusaStoreRepository
entity you created.Copy to clipboardStore
Create index.d.ts
Finally, to ensure TypeScript is aware of the new type you’re creating, create the file
with the following content:Copy to clipboardsrc/index.d.ts
You’ll later add relations to the
interface as you add them to theCopy to clipboardStore
entity. You’ll also be using the same file when extending other entities.Copy to clipboardStore
Extend the User Entity
Next, you’ll extend the user entity to add a new column and relation to the
entity. The steps are the same as the ones described in the previous section.Copy to clipboardStore
Start by creating the
file with the following content:Copy to clipboardsrc/models/user.ts
This imports the
entity from the Medusa core package asCopy to clipboardUser
and creates a new entityCopy to clipboardMedusaUser
that extends it. In the entity, you add a new columnCopy to clipboardUser
and a relationCopy to clipboardstore_id
to theCopy to clipboardstore
entity.Copy to clipboardStore
Then, in the
entity created inCopy to clipboardStore
, add the following relation to the entity:Copy to clipboardsrc/models/store.ts
You might see some TypeScript errors in your editor now. This can be resolved by replacing the content of the
with the following content:Copy to clipboardsrc/index.d.ts
This informs TypeScript that the
entity now hasCopy to clipboardUser
andCopy to clipboardstore_id
properties, and theCopy to clipboardstore
entity has aCopy to clipboardStore
property. You can learn more here.Copy to clipboardmembers
Extend the UserRepository
Next, create the file
with the following content:Copy to clipboardsrc/repositories/user.ts
This imports the
from the Medusa core package asCopy to clipboardUserRepository
and creates a newCopy to clipboardMedusaUserRepository
that extends it but targets the newCopy to clipboardUserRepository
entity.Copy to clipboardUser
Create Migration for the User Entity
Since you’re adding a new column
to theCopy to clipboardstore_id
entity, you must add a new migration that reflects that column in your database.Copy to clipboardUser
You can learn more about migrations in our documentation.
First, run the following command in the root directory of your backend:
This will create a new file under
. The file’s name should be of the formatCopy to clipboardsrc/migrations
.Copy to clipboard<TIMESTAMP>_UserChanged.ts
In the file, there’s a migration class with two methods:
andCopy to clipboardup
. Replace the methods with the following content:Copy to clipboarddown
Before you run the migrations, run the
command which transpiles the files under theCopy to clipboardbuild
directory into theCopy to clipboardsrc
directory:Copy to clipboarddist
Then, run the migration command:
This will add the new
column to theCopy to clipboardstore_id
table.Copy to clipboarduser
Create Middleware to Register Logged-In User
To get access to the logged-in user across your services, you need to register it through a middleware. This middleware will run on all endpoints under the
path, except for theCopy to clipboard/admin
endpoints.Copy to clipboard/admin/auth
You can learn more about middlewares in our documentation.
Start by creating the file
with the following content:Copy to clipboardsrc/api/middlewares/logged-in-user.ts
This retrieves the logged-in user from
and, if available, registers it in Medusa’s dependency container underCopy to clipboardreq.user.userId
.Copy to clipboardloggedInUser
You can learn more about the dependency container and injection in our documentation.
Next, create the file
with the following content:Copy to clipboardsrc/api/index.ts
This registers the middleware on all paths starting with
except forCopy to clipboard/admin
paths. Notice that you also add theCopy to clipboard/admin/auth
middleware before theCopy to clipboardauthenticate
middleware. TheCopy to clipboardregisterLoggedInUser
middleware authenticates the user and setsCopy to clipboardauthenticate
. If not added, theCopy to clipboardreq.user
middleware will not be able to access the logged-in user.Copy to clipboardregisterLoggedInUser
You’ll see the middleware’s work in action when you start customizing services in the upcoming sections.
Extend Store Service
In this section, you’ll extend the
service to change the logic behind retrieving a store. In the Medusa core package, it’s assumed there’s one store and so the first store is retrieved. You’ll be changing that to retrieve the store of the logged-in user.Copy to clipboardStore
You can learn more about extending services in the documentation.
Create the file
with the following content:Copy to clipboardsrc/services/store.ts
You import the
from the Medusa core package asCopy to clipboardStoreService
. You then create a newCopy to clipboardMedusaStoreService
class that extendsCopy to clipboardStoreService
.Copy to clipboardMedusaStoreService
In the class, you set the
of the service toCopy to clipboardLIFE_TIME
. This is necessary for the service to access the registeredCopy to clipboardLifetime.SCOPED
. You can learn more about it in the documentation.Copy to clipboardloggedInUser
You also add a new class attribute
and set its value in the constructor. Notice that you wrap the initialization ofCopy to clipboardloggedInUser_
in the constructor with a try-catch block to avoid errors when theCopy to clipboardloggedInUser_
is not registered in the Medusa container.Copy to clipboardloggedInUser
You then override the
method. In that method:Copy to clipboardretrieve
- You check if the
is set. If not, you call theCopy to clipboardloggedInUser_
method of the service from the core package.Copy to clipboardretrieve
- If the
is set, you retrieve the store using a new methodCopy to clipboardloggedInUser_
. This method retrieves the store using the value ofCopy to clipboardretrieveForLoggedInUser
of theCopy to clipboardstore_id
and expands theCopy to clipboardloggedInUser_
relation.Copy to clipboardmembers
Before you test out this method, you should create the logic that associate a new user with a new store.
Extend the User Service
In this section, you’ll implement the logic behind creating a store for every new user. To do this, you’ll extend the
from the Medusa core to override theCopy to clipboardUserService
method.Copy to clipboardcreate
Create the file
with the following content:Copy to clipboardsrc/services/user.ts
In this file you:
- Extend the core’s
which is imported asCopy to clipboardUserService
.Copy to clipboardMedusaUserService
- You change the value of the
attribute of the service. This is explained in the Extend Store Service section.Copy to clipboardLIFE_TIME
- You add a new
attribute and initialize it in the constructor.Copy to clipboardloggedInUser_
- You override the
method. In the method, you first check if theCopy to clipboardcreate
does not have aCopy to clipboarduser
. This allows you to specifically set the store of the user in other places if necessary. This can be helpful if you’re creating a team of users in one store. If the user doesn’t haveCopy to clipboardstore_id
set, a new store is created and theCopy to clipboardstore_id
property of the user is set to the ID of the new store. The user is saved then using the logic implemented in the core service.Copy to clipboardstore_id
Test Store-User Relation
Time to test everything you’ve implemented so far. To do that, run the
and theCopy to clipboardbuild
commands in your Medusa backend:Copy to clipboardstart
Once your Medusa backend starts, login with any admin user that you have using the User Login endpoint. If you’ve seeded your database with demo data, you should have the following login credentials:
Notice that when you log in, the
of the user is set toCopy to clipboardstore_id
.Copy to clipboardnull
Then, try to get the store’s data using the Get Store Details endpoint. The default store will be returned, which has an empty
array.Copy to clipboardmembers
Next, try to create a new user using the Create a User endpoint. You should see that the new user has a set
.Copy to clipboardstore_id
Now, try to login with your new user, then try to retrieve the store’s data using the Get Store Details endpoint as mentioned earlier. You’ll see now that a different store is retrieved than the first time. This is the store that was created for this new user. It also has the new user as part of its
array.Copy to clipboardmembers
The user-store relation is now established and ready for use. Next, you’ll be working on the product-store relation.
Extend the Product Entity
In this section, you’ll extend the
entity to add a new column and relation to theCopy to clipboardProduct
. The process will be very similar to that of extending theCopy to clipboardStore
entity.Copy to clipboardUser
Start by creating the file
with the following content:Copy to clipboardsrc/models/product.ts
This imports the
entity from the Medusa core package asCopy to clipboardProduct
and creates a new entityCopy to clipboardMedusaProduct
that extends it. In the entity, you add a new columnCopy to clipboardProduct
and a relationCopy to clipboardstore_id
to theCopy to clipboardstore
entity.Copy to clipboardStore
Then, in the
entity created inCopy to clipboardStore
, add the following relation to the entity:Copy to clipboardsrc/models/store.ts
You might see some TypeScript errors in your editor now. This can be resolved by adding the
relation to theCopy to clipboardproducts
interface and adding a new declaration for theCopy to clipboardStore
inCopy to clipboardProduct
:Copy to clipboardindex.d.ts
Extend the ProductRepository
Next, create the file
with the following content:Copy to clipboardsrc/repositories/product.ts
This imports the
from the Medusa core package asCopy to clipboardProductRepository
and creates a newCopy to clipboardMedusaProductRepository
that extends it but targets the newCopy to clipboardProductRepository
entity.Copy to clipboardProduct
Create Migration for the Product Entity
Since you’re adding a new column
to theCopy to clipboardstore_id
entity, you must add a new migration that reflects that column in your database.Copy to clipboardProduct
First, run the following command in the root directory of your backend:
This will create a new file under
. The file’s name should be of the formatCopy to clipboardsrc/migrations
.Copy to clipboard<TIMESTAMP>_ProductChanged.ts
In the file, there’s a migration class with two methods:
andCopy to clipboardup
. Replace the methods with the following content:Copy to clipboarddown
Before you run the migrations, run the
command which transpiles the files under theCopy to clipboardbuild
directory into theCopy to clipboardsrc
directory:Copy to clipboarddist
Then, run the migration command:
This will add the new
column to theCopy to clipboardstore_id
table.Copy to clipboardproduct
Extend the Product Service
In this section, you’ll extend the product service to do two things:
- Change the
logic to attach the product to the logged-in user’s store.Copy to clipboardsave
- Change the methods used to retrieve products including the
,Copy to clipboardlist
, andCopy to clipboardlistAndCount
methods to retrieve the products only associated with the logged-in user’s store.Copy to clipboardretrieve
Create the file
with the following content:Copy to clipboardsrc/services/product.ts
In this file you:
- Extend the core’s
which is imported asCopy to clipboardProductService
.Copy to clipboardMedusaProductService
- You change the value of the
attribute of the service. This is explained in the Extend Store Service section.Copy to clipboardLIFE_TIME
- You add a new
attribute and initialize it in the constructor.Copy to clipboardloggedInUser_
- You override the
andCopy to clipboardlist
methods to filter the retrieved products by the logged-in user’s store ID. You also ensure that theCopy to clipboardlistAndCount
attribute is retrieved as part of the product and that theCopy to clipboardstore_id
relation is expanded.Copy to clipboardstore
- You override the
method to check if the retrieved product belongs to the logged-in user’s store. If not, you throw an error that the product does not exist.Copy to clipboardretrieve
- You override the
method to check if the logged-in user has a store and, if so, associate the product’s store ID with that store.Copy to clipboardcreate
Notice that you didn’t implement themechanism as part of the repository this time. This is because the repository does not have access to the Medusa container. So, you won’t be able to access the logged-in user in it.Copy to clipboardsave
Test the Product-Store Relation
You can now test the relation between the product and the store.
Start by running the
andCopy to clipboardbuild
commands in the root of your Medusa backend:Copy to clipboardstart
Then, log in as a user who has a store as explained in the previous testing section.
After that, retrieve the available products by sending a request to the List Products endpoint. You’ll see that there are no products returned, even if you previously had products in your store. This is because these products are not associated with the user’s store.
Then, try creating a new product using the Create Product endpoint. You’ll see that the created product has a
attribute. TheCopy to clipboardstore
of that store is the same ID of the user’s store.Copy to clipboardid
If you try retrieving the list of products again, you’ll find your newly-created product in the returned array.
Note About Super-Admin User
So far in your implementation you’ve taken into accoun utsers that don’t have a set store ID. These are considered “super-admin” users. These users would be able to view all products in the store and the default store’s details.
If this logic does not work for you, make sure to change the implementation to require a store ID for a user in the different locations we’ve used it.
What’s Next
In this tutorial, you learned how to implement the foundation of a marketplace: having multiple stores, different users within those stores, and associating products with a store.
A marketplace has a variety of other features, each depending on your use case. Some of them are:
- Create order-store relation: This requires a similar implementation as what you’ve done in this tutorial with products and users. You need to extend the
entity to include a relation to the store. You can learn more about extending entities in the documentation.Copy to clipboardOrder
- List orders by stores: This requires a similar implementation as what you’ve done in this tutorial with products. You need to extend the
to override the methods used to retrieve orders. You can learn more about extending services in the documentation.Copy to clipboardOrderService
- Associate an order to a store: This requires listening to the
event in a subscriber. The implementation can include creating child orders of an order if in your use case you support have products from multiple stores in one product. In this case, you’d also need to extend the order entity to create a parent-child relation. You can learn more about subscribers in the documentation.Copy to clipboardorder.created
- Implement teams within a store: You can implement a team within a store by extending the
entity to associate it with a store ID, then associate the user created from the invite with that store ID.Copy to clipboardInvite
These are just some ideas and direction for your development of the marketplace. For further help in your development, make sure to check out the available guides in our documentation.
Share this post
Try Medusa
Spin up your environment in a few minutes.

You may also like
On this page