- Moved recipes into its own section of the learning resources
- Updated the marketplace recipe overview
- Added an example implementation of the marketplace recipe
Closes DOCS-791
This recipe provides the general steps to implement a marketplace in your Medusa application.
<Note type="soon" title="In Development">
This recipe is a work in progress, as some features are not ready yet in Medusa V2.
</Note>
## Overview
A marketplace is an online commerce store that allows different vendors to sell their products within the same commerce system. Customers can purchase products from any of these vendors, and vendors can manage their orders separately.
In Medusa, you can create a Marketplace Module that establishes the relations between a store, users, products, and orders. Using these relations, you can implement different stores for different users, with products and orders associated with that store.
In Medusa, you can create a Marketplace Module that implements custom data models, such as vendors or sellers, and link those data models to existing ones such as products and orders. You also expose custom features using API routes, and implement complex flows using workflows.
<Note title="Related use-case">
@@ -28,783 +22,107 @@ In Medusa, you can create a Marketplace Module that establishes the relations be
---
## Create Relationships Between Data Models
## Create Custom Module with Data Models
In a marketplace, an admin user has a store where they manage their products and orders, among other details.
In a marketplace, a business or a vendor has a user, and they can use that user to authenticate and manage the vendor's data.
Create a Marketplace Module that holds and manages these relationships.
<Note type="soon">
Module Relationships is coming soon.
</Note>
You can create a marketplace module that implements data models for vendors, their admins, anything else that fits your use case.
This creates a `StoreOrder` data model with the `store_id`, `order_id`, and `parent_order_id` properties.
{/* The `store_id` and `order_id` properties will be used to establish relationships to the Store and Order modules. You’ll learn about the use of `parent_order_id` in a later section. */}
To reflect these changes on the database, create the migration `src/modules/marketplace/migrations/Migration20240514143248.ts` with the following content:
export class Migration20240514143248 extends Migration {
async up(): Promise<void> {
this.addSql("create table if not exists \"store_order\" (\"id\" varchar(255) not null, \"store_id\" text not null, \"order_id\" text not null, \"parent_order_id\" text not null, constraint \"store_order_pkey\" primary key (\"id\"));")
this.addSql("create table if not exists \"store_product\" (\"id\" varchar(255) not null, \"store_id\" text not null, \"product_id\" text not null, constraint \"store_product_pkey\" primary key (\"id\"));")
this.addSql("create table if not exists \"store_user\" (\"id\" varchar(255) not null, \"store_id\" text not null, \"user_id\" text not null, constraint \"store_user_pkey\" primary key (\"id\"));")
}
async down(): Promise<void> {
this.addSql("drop table if exists \"store_order\" cascade;")
this.addSql("drop table if exists \"store_product\" cascade;")
this.addSql("drop table if exists \"store_user\" cascade;")
}
}
```
You’ll run the migration after registering the module in the Medusa configurations.
Then, create the module’s main service at `src/modules/marketplace/service.ts` with the following content:
export const mainServiceHighlights = [
["6", "MedusaService", "Extends the service factory to generate data management features."]
class MarketplaceModuleService extends MedusaService({
StoreUser,
StoreProduct,
StoreOrder,
}){
// TODO add custom methods
}
export default MarketplaceModuleService
```
The module’s main service extends the service factory to generate data management features for the `StoreUser`, `StoreProduct`, and `StoreOrder` data models.
Finally, create the module definition at `src/modules/marketplace/index.ts` with the following content:
```ts title="src/modules/marketplace/index.ts"
import MarketplaceModuleService from "./service"
import { Module } from "@medusajs/utils"
export default Module("marketplace", {
service: MarketplaceModuleService,
})
```
To use the module, add it to the `modules` object in `medusa-config.js`:
```js title="medusa-config.js"
module.exports = defineConfig({
// ...
modules: {
marketplaceModuleService: {
resolve: "./modules/marketplace",
definition: {
isQueryable: true,
},
},
},
})
```
Then, run the migrations of the module:
```bash npm2yarn
npx medusa migrations run
```
</Details>
---
## Attach Users to Stores
## Define Module Links
To attach admin users to their own stores, create a subscriber that listens to the `user.created` event and attaches the user to the store.
Since a vendor has products, orders, and other models based on your use case, define module links between your module's data models and the commerce module's data models.
<Note type="soon">
- The `user.created` event is currently not emitted.
- Module Relationships is coming soon.
</Note>
For example, if you defined a vendor data model in a marketplace module, you can define a module link between the vendor and the Product Module's product data model.
<Card
href="!docs!/basics/events-and-subscribers"
title="Create a Subscriber"
text="Learn how to create a subscriber in Medusa."
This adds a subscriber to the `user.created` event. In the subscriber, you:
- Retrieve the created user. The created user’s ID is passed in the event’s data payload.
- Create a store for that user using the Store Module.
- Create a relationship between the user and the store by creating a `StoreUser` record.
To test it out, use the `medusa user` command to create a user:
```bash npm2yarn
npx medusa user -e my-admin@medusa-test.com -p supersecret
```
At the end of the output, you should see the message `Created StoreUser {store_user_id}` where the `{store_user_id}` is the ID of the created `StoreUser`.
</Details> */}
---
## Attach Products to Stores
## Allow Vendor Admins to Authenticate
Similar to the previous section, to attach products to stores, create a subscriber that listens to the `product.created` event. In the subscriber, you attach the product to the store it’s created in.
If your marketplace module implements admins for sellers or vendors, you can create an actor type for it.
<Note type="soon">
- The `product.created` event is currently not emitted.
- Module Relationships is coming soon.
</Note>
Then, you guard custom API routes to only allow authenticated vendor admins.
<Card
href="!docs!/basics/events-and-subscribers"
title="Create a Subscriber"
text="Learn how to create a subscriber in Medusa."
href="/commerce-modules/auth/create-actor-type"
title="Create an Actor Type"
text="Learn how to create an actor type and authenticate it."
startIcon={<AcademicCapSolid />}
showLinkIcon={false}
/>
{/* <Details summaryContent="Example">
Create the file `src/subscribers/product-created.ts` with the following content:
export const productSubscriberHighlights = [
["24", "retrieveProduct", "Retrieve the created product."],
["26", "", "This subscriber requires the store ID to be set in `product.metadata.store_id`. If not, the subscriber ends execution."],
["30", "createStoreProducts", "Create a relationship between the product and the store by creating a `StoreProduct` record."]
This adds a subscriber to the `product.created` event. In the subscriber, you:
- Retrieve the created product.
- This subscriber requires the store ID to be set in `product.metadata.store_id`. If not, the subscriber ends execution.
- If the store ID is found, create a relationship between the product and the store by creating a `StoreProduct` record.
To test it out, start the Medusa application:
```bash npm2yarn
npm run dev
```
Then, send an authenticated `POST` request to `/admin/products`:
```bash
curl -X POST 'http://localhost:9000/admin/products' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {jwt_token}' \
--data '{
"title": "My Shirt 13",
"metadata": {
"store_id": "store_01HXV74JCCAHA3F2X3EWZTZG5R"
}
}'
```
This returns the created product. In the next section, you’ll implement the API route to fetch the products of a store.
</Details> */}
---
## Retrieve Store’s Products
## Create Vendor API Routes
To allow admin users to view their store’s products, create an API route that uses the remote query to fetch the products based on the logged-in user’s store.
<Note type="soon">
Retrieving module relationship data using the remote query is coming soon.
</Note>
If you provide vendor or sellers with custom features, or a different way of managing products and orders, create API routes only accessible by vendors exposing your custom features.
<CardList items={[
{
href: "!docs!/basics/api-routes",
title: "API Routes",
title: "Create API Routes",
text: "Learn how to create an API Route in Medusa.",
This will return the product you created in the previous section, if the `{jwt_token}` belongs to the user of the same store ID.
</Details> */}
---
## Split Orders Based on Stores
## Split Orders Based on Vendors
An order may contain items from different stores. To ensure that users can only view and manage their orders, create a subscriber that listens to the `order.placed` event and handles splitting the order into multiple orders based on the items’ stores.
If your use case allows a customer's orders to have items from different vendors, you can override the [Complete Cart API route](https://docs.medusajs.com/v2/api/store#carts_postcartsidcomplete) to change how the order should be created.
<Note type="soon">
The `order.placed` event is still not emitted in Medusa V2.
</Note>
If your flow requires different steps, such as creating a parent order then creating a child order, create a workflow and execute it from the API route.
<Card
href="!docs!/basics/events-and-subscribers"
title="Create a Subscriber"
text="Learn how to create a subscriber in Medusa."
href="!docs!/basics/workflows"
title="Create a Workflow"
text="Learn how to create a workflow in Medusa."
startIcon={<AcademicCapSolid />}
showLinkIcon={false}
/>
{/* <Details summaryContent="Example">
Create the file `src/subscribers/order-created.ts` with the following content:
export const orderSubscriberHighlights = [
["35", "", "Loop over the created order’s items."],
["57", "", "Group the items by their store ID."],
["72", "createStoreOrders", "If the items have the same store ID, then associate the created order with the store."],
["88", "createStoreOrders", "If there are items from more than one store in the order, create child orders and associate each of them with the store."],
["91", "parent_order_id", "The `parent_order_id` property in the `StoreOrder` data model points to the original order."]
This adds a subscriber to the `order.placed` event. In the subscriber, you:
- Loop over the created order’s items.
- Group the items by their store ID.
- If the items have the same store ID, then associate the created order with the store.
- If there are items from more than one store in the order, create child orders and associate each of them with the store. Here, you use the `parent_order_id` property in the `StoreOrder` data model to point to the original order.
To test this out, create an order in your store. That will run the subscriber and create the child orders.
The next section covers how to retrieve the store’s orders.
</Details> */}
---
## Retrieve Store’s Orders
Similar to products, to allow admin users to view their store’s orders, create an API route that uses the remote query to fetch the orders based on the logged-in user’s store.
<Note type="soon">
Retrieving module relationship data using the remote query is coming soon.
</Note>
<CardList items={[
{
href: "!docs!/basics/api-routes",
title: "API Routes",
text: "Learn how to create an API Route in Medusa.",
This creates a `GET` API route at `/admin/marketplace/orders`. In the API route, you:
- Retrieve the store of the logged-in user.
- Build a query that retrieves the orders of that store.
- Retrieve the orders using remote query and return them.
To test it out, start the Medusa application. Then, send a `GET` request to `/admin/marketplace/orders`:
```bash
curl 'localhost:9000/admin/marketplace/orders' \
-H 'Authorization: Bearer {jwt_token}'
```
This will return the orders you created in the previous section if the `{jwt_token}` belongs to the user of the same store ID.
</Details> */}
---
## Customize Storefront
Medusa provides a Next.js storefront to use with your application. You can either customize it or build your own to represent your marketplace features.
Medusa provides a Next.js Starter storefront that you can customize for your use case.
For example, you can create an API route that retrieves available stores and another API route that retrieves the products of each store using the remote query as done in previous sections.
You can also create a custom storefront from scratch.
<CardList items={[
{
@@ -815,10 +133,18 @@ For example, you can create an API route that retrieves available stores and ano
showLinkIcon: false
},
{
href: "!docs!/storefront-development/tips",
title: "Storefront Tips",
text: "Find tips on developing a custom storefront.",
href: "/storefront-development",
title: "Storefront Development",
text: "Find useful guides for creating a custom storefront.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
---
## Customize Admin
The Medusa Admin is extendable, allowing you to add widgets to existing pages or create new pages.
If your use case requires bigger customizations to the admin, such as showing different products and orders based on the logged-in vendor, use the [admin API routes](!api!/admin) to build a custom admin.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.