Medusa Marketplace #1 | Let's follow THE recipe
In this first part, we will cover the official Medusa recipe and add some tiny adjustments for our Marketplace needs.

Search for a command to run...
In this first part, we will cover the official Medusa recipe and add some tiny adjustments for our Marketplace needs.

( Franciscohack @ qualityservice . com ) FRANCISCOHACK @ Q UALITYSERVICE . COM
The best recovery team to assist you in reclaiming your money from these nefarious individuals is F r a n c i s c o H a c k .A few months ago, a man online got in touch with me and told me about a bitcoin investing platform that promised large returns. I put $198,000USD into this platform before being asked to put in more money without being allowed to withdraw from the previous investment. My money was being stolen, and I knew it. They stopped returning my calls and messages since my money was gone. Thank goodness, a buddy of mine informed me about F r a n c i s c o H a c k and how they could help me get my money back that had been stolen. After contacting them, my money was returned to me in less than 48 hours. F r a n c i s c o H a c k, I sincerely appreciate your assistance. Align with them at any of the links below for more details: Whatsapp +4 4 7 5 6 1 1 6 9 0 4 3 Telegram @ Franciscohack
This is an awesome guide, and I greatly appreciate the work you have put into it! I have been following along and trying out some different things to customize a marketplace, and I am wondering: Why did we not follow the step from the recipe "Extend the UserRepository to point to your extended entity". Is this approach specific to a newer version? or is there a another reason why this is not necessary? Any extra context on this would be great.
Hey Kyle, I recently decided to remove repositories, as they are not/no longer useful compared to previous versions of Medusa with Medusa Extender for example.
Today we can afford not to extend the repository; however; if you have a specific need with a repository, you can of course extend it.
oh, I see. Thank you.
Hello! Nice work. Please can you tell me why calling http://localhost:7001/admin/auth returns Cannot POST /admin/auth on Postman?
Hi Mirzac Marius Thanks for your feedback!
Basically, you are using the wrong port :7001 is used for the admin ui in general, you should point directly to :9000 when you want to target the API
CONSULT A LICENSE PATECH RECOVERY HACKER FOR CRYPTO RECOVERY I would like to express my gratitude to PATECH RECOVERY HACKER for helping me through a difficult period. I was duped into making an online investment in which I was promised a 25% weekly profit, but it turned out to be a scam. I was very angry until I read an article about PATECH Recovery about how they had assisted others in recovering funds. But because Patech Recovery helped me and made things simple for me, I was able to get my cryptocurrency back. If you've been a victim of something similar, get in touch with them Email: patechrecovery @ proton dot me whatsapp---913////730///0531
Show less
CONSULT A LICENSE PATECH RECOVERY HACKER FOR CRYPTO RECOVERY I would like to express my gratitude to PATECH RECOVERY HACKER for helping me through a difficult period. I was duped into making an online investment in which I was promised a 25% weekly profit, but it turned out to be a scam. I was very angry until I read an article about PATECH Recovery about how they had assisted others in recovering funds. But because Patech Recovery helped me and made things simple for me, I was able to get my cryptocurrency back. If you've been a victim of something similar, get in touch with them Email: patechrecovery @ proton dot me whatsapp---913////730///0531
Show less
Thanks for sharing you knowledge and your time,it help me a lot to know a medusajs.I am new to medusa when I request to /admin/users in postman, I get 401 Unauthorized,what I miss is there any token or session to setup?
Hey Tewodros!
Make sure to log in your Admin Route before sending a POST request to the /admin/users route :

Here is also a link to the docs : https://docs.medusajs.com/modules/users/admin/manage-profile#user-login
When you created your Medusa application for the first time, you had to be redirected by the server to change your password.
In this series, I'm going to cover a whole host of points covered in the Medusa.js documentation or not to have a functional marketplace using Medusa.js and Stripe Connect.
Hello everyone! In this part of our Medusa.js marketplace journey, we'll continue building on the groundwork we established in the previous sections. This time, our focus will be on extending the StoreService and ProductService to ensure a seamless u...
Say hello to our new home

Hello everyone! In this new part, we'll be setting up Stripe and Stripe Connect to collect payments from our customers and trigger payments for our vendors. ⚠ Before starting this part, make sure you have a valid Stripe account and have set up Strip...

Hello everyone! In this new section, we'll take a look at payments in preparation for what's to come. What's the goal here? In the same spirit as Orders, we're going to divide a single Payment into several for each store, or to be more transparent, w...

Hello everyone! Many of you have been waiting for this part, so here we're going to cover how to manage orders for each seller. Indeed, the most logical and widely used way is the one outlined by Shahed Nasserin her tutorial series that inspired pers...

Hello everyone! Now that we've extended the ShippingOption and ShippingProfile entities, we can start integrating new features into our services. What's the goal here ? The aim of this part is to override various functions of the ShippingOption and S...

Hello Everyone,
My name is Adil (a.k.aadevinwild), and I'm passionate about Medusa.js.
Last year, I embarked on my first (client) project using Medusa.js to build a marketplace. Since then, Medusa has undergone significant improvements, and I believe it's beneficial to share insights on constructing a marketplace with the latest versions.
A marketplace, conceptually, is a platform where multiple vendors can sell various products to numerous customers. One key aspect I've learned is that typically, the marketplace retains funds until certain conditions are met (e.g an order has been delivered to the customer), after which payouts are made to vendors, including fees.
In this blog series, we'll delve into building a specific type of marketplace—a clothing marketplace where anyone can register and start selling their products.
While we won't cover every detail, the aim is to equip you with the knowledge to customize Medusa.js for creating a simple yet functional marketplace.
You'll likely need to manage many aspects independently, but this guide will prepare you well.
It's important to note that not every marketplace will be identical, and the provided examples might not exactly match the marketplace you envision.
Let's start by following the recipe detailed in the Medusa documentation. The documentation covers various concepts and sometimes includes actual code implementations.
This will be a heavy first part, as it will condense certain points from the official documentation, but I promise that the rest of the parts will be lighter in terms of text.
As we progress through this series, we will be enhancing and altering some of Medusa's fundamental logic and functionality.
However, the ability to use the sales channels feature flag may be impossible upon the design approach taken.
The current design of the Cart system, particularly its integration with sales channels, poses a challenge. It is structured in a way that precludes the possibility of associating multiple sales channels with a single Cart.
If you wish to leverage the sales channels feature, you have the flexibility to modify the Cart system or develop a similar mechanism to fully capitalize on its capabilities.
You'll learn how to disable the sales_channels feature flag by clicking here.
The first part of our journey involves extending entities. The documentation guides us on how to begin this process, starting with the concept of a User linked to a Store (where the User acts as a vendor, it's distinct from a Customer).
Here’s how we can start extending the User entity:
// src/models/user.ts
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
} from "typeorm"
import {
User as MedusaUser,
} from "@medusajs/medusa"
import { Store } from "./store"
@Entity()
export class User extends MedusaUser {
@Index("UserStoreId")
@Column({ nullable: true })
store_id?: string
@ManyToOne(() => Store, (store) => store.members)
@JoinColumn({ name: "store_id", referencedColumnName: "id" })
store?: Store
}
Next, we extend the Store entity to include a new property representing our OneToMany relationship, which is necessary to avoid type errors in the previously extended User model :
// src/models/store.ts
import { Entity, OneToMany } from 'typeorm';
import { Store as MedusaStore } from '@medusajs/medusa';
import { User } from './user';
@Entity()
export class Store extends MedusaStore {
@OneToMany(() => User, (user) => user.store)
members?: User[];
}
After extending the User and Store entities, we proceed with a database migration to reflect these changes in the schema. We continue following the recipe by executing the following command in our terminal:
npx typeorm migration:create src/migrations/add-user-store-id
Here's how we replace the up and down functions in our new migration with the ones provided in the docs :
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" ADD "store_id" character varying`
);
await queryRunner.query(
`CREATE INDEX "UserStoreId" ON "user" ("store_id")`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "public"."UserStoreId"`
);
await queryRunner.query(
`ALTER TABLE "user" DROP COLUMN "store_id"`
);
}
To apply these migrations, we build our server and run the CLI command:
yarn build
npx medusa migrations run
If we check our database now, we should see a new store_id column in the user table!

We have just set up the foundation for our marketplace by extending the User and Store entities and creating the necessary migrations. Now, we'll dive deeper into customizing the data management functionalities using middleware and service extensions.
Medusa.js provides a way to create middleware that can be applied to specific routes. In the context of our marketplace, we want to ensure that the logged-in user's information is available throughout our application, especially when retrieving data. Let's create a new middleware that will register the logged-in user in the request scope:
// src/api/middlewares.ts
import {
authenticate,
} from "@medusajs/medusa"
import type {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
MiddlewaresConfig,
User,
UserService,
} from "@medusajs/medusa"
const registerLoggedInUser = async (
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction
) => {
let loggedInUser: User | null = null
if (req.user && req.user.userId) {
const userService =
req.scope.resolve("userService") as UserService
loggedInUser = await userService.retrieve(req.user.userId)
}
req.scope.register({
loggedInUser: {
resolve: () => loggedInUser,
},
})
next()
}
export const config: MiddlewaresConfig = {
routes: [
{
matcher: /^\/admin\/(?!auth|analytics-config|users\/reset-password|users\/password-token|invites\/accept).*/,
middlewares: [authenticate(), registerLoggedInUser],
},
],
}
In this middleware, we first check if there is a logged-in user in the request. If so, we retrieve the user's information using the userService.
We then register the loggedInUser value in the request scope, which can be accessed by other services and components.
/admin routes, except for a few specific routes that don't require authentication (This is why the long regex)Next, let's extend the UserService to automatically create a new store when a new user is registered. This will ensure that every user has a store associated with their account, which is a key requirement for our marketplace :
// src/services/user.ts
import { Lifetime } from "awilix"
import {
UserService as MedusaUserService,
} from "@medusajs/medusa"
import { User } from "../models/user"
import {
CreateUserInput as MedusaCreateUserInput,
} from "@medusajs/medusa/dist/types/user"
import type StoreRepository from "@medusajs/medusa/dist/repositories/store"
type CreateUserInput = {
store_id?: string
} & MedusaCreateUserInput
class UserService extends MedusaUserService {
static LIFE_TIME = Lifetime.SCOPED
protected readonly loggedInUser_: User | null
protected readonly storeRepository_: typeof StoreRepository
constructor(container, options) {
// @ts-ignore
super(...arguments)
this.storeRepository_ = container.storeRepository
try {
this.loggedInUser_ = container.loggedInUser
} catch (e) {
// avoid errors when backend first runs
}
}
async create(
user: CreateUserInput,
password: string
): Promise<User> {
if (!user.store_id) {
const storeRepo = this.manager_.withRepository(
this.storeRepository_
)
let newStore = storeRepo.create()
newStore = await storeRepo.save(newStore)
user.store_id = newStore.id
}
return await super.create(user, password)
}
}
export default UserService
In this extended UserService, we override the create method to check if the user has a store_id associated with their account. If not, we create a new store and associate it with the user's account before creating the new user.
Let's run our server and try to create a new User, we can use Postman or HTTPie to make a POST request to /admin/users when logged-in.
Let's try with a simple payload like this one :
// [POST] /admin/users
{
"email":"john@doe.com",
"password":"1234"
}
You should get a server's response like this one :
{
"user": {
"email":"john@doe.com",
"store_id":"store_01HX6NFWW7R7306VHQ1PSP8YN7",
// ✅ ☝️ Just what we wanted!
"id":"usr_01HX6NFX8G78KHB6KD3MDS69VH",
"role":"member",
"first_name":null,
"last_name":null,
"api_token":null,
"metadata":null,
"created_at":"2024-05-06T09:59:19.1182",
"updated_at":"2024-05-06T09:59:19.118Z",
"deleted_at":null
}
}
Now, whenever we create a new User a new Store is associated to it !
And this is where this first part of the series ends, as the recipe gives us a few more concepts, such as associating a store_id with the creation of a product etc., before moving on to the events/subscribers.
We're not going to tackle events just yet, but rather continue to focus on service management and how to extend them so as to have everything nicely tied up to a vendor.
I have CORS errors since I've added middlewares
If you have any CORS errors when accessing the Admin UI, this might solves your issue
ADMIN_CORS in your .env fileimport { authenticate, type MiddlewaresConfig } from '@medusajs/medusa'
import { parseCorsOrigins } from 'medusa-core-utils'
import * as cors from 'cors' // ⚠️ Make sure you import it like this
import registerLoggedInUser from './middlewares/register-logged-in-user'
export const config: MiddlewaresConfig = {
routes: [
{
matcher: /^\/admin\/(?!auth|analytics-config|users\/reset-password|users\/password-token|invites\/accept).*/,
middlewares: [
cors.default({ credentials: true, origin: parseCorsOrigins(process.env.ADMIN_CORS ?? '') }),
authenticate(),
registerLoggedInUser,
],
},
],
}
MyloggedInUserisundefinedornull
If you have this issue and are sure that you are logged in before making a request, please update your LIFE_TIME services to TRANSIENT, it should fix the issues :
// An example with the UserService extended earlier
class UserService extends MedusaUserService {
static LIFE_TIME = Lifetime.TRANSIENT
// ...
}
You can access the complete part's code here.
You can contact me on Discord and X with the same username : @adevinwild