Blog

September 17, 2025·Product

Implement Booking System in Medusa

Shahed Nasser

Shahed  avatar

Shahed Nasser

Learn how to set up a ticketing booking system in Medusa, with full admin and storefront customizations.

Image modal

Booking systems can be used in many different commerce cases. In this tutorial, we will show how to set up a ticket booking system that allows customers to book tickets for events such as shows or sports games. Businesses selling tickets require a commerce platform that supports managing venues, shows, seat availability, and other features that enable the business to sell tickets reliably and efficiently.

Medusa is a digital commerce platform with a built-in Framework for customization. Using Medusa, developers can implement custom data models and business logic on top of existing core commerce features, allowing them to build a powerful ticket booking system.

Implement a Ticket Booking System in Medusa
Follow this step-by-step tutorial

Get Started

Create Ticket Booking Data Models

To store and manage ticket-booking data, you need a Ticket Booking Module that defines data models related to venues, ticket products, and ticket purchases. The module's service will provide data management features for these data models.

Then, you can link your custom and Medusa's data models (defined in Commerce Modules) to build on top of existing features. For example, you can link a ticket product variant to a product variant, allowing you to leverage existing pricing and inventory features for your ticket products.

Image modal

Customize the Medusa Admin Dashboard

You can extend the Medusa Admin dashboard to add pages and forms for managing ticket-related data. For example, you can add pages to manage venues and show dates.

Inject Custom Validation in Cart Operations

Medusa's workflows provide hooks that allow you to inject custom logic at specific points in the workflow execution.

Specifically, workflows like Copy to clipboardaddToCartWorkflow and Copy to clipboardcompleteCartWorkflow have a Copy to clipboardvalidate hook that you can consume to add custom validation logic. You can validate that the selected seat is available before adding a ticket to the cart.

import { addToCartWorkflow } from "@medusajs/medusa/core-flows"
import { MedusaError } from "@medusajs/framework/utils"
import { validateSeatAvailability } from "../../utils"
addToCartWorkflow.hooks.validate(
async ({ input }, { container }) => {
const items = input.items
const query = container.resolve("query")
const { data: productVariants } = await query.graph({
entity: "product_variant",
fields: ["id", "product_id", "ticket_product_variant.purchases.*"],
filters: {
id: items.map((item) => item.variant_id).filter(Boolean) as string[],
},
})
// Check each item being added to cart
for (const item of items) {
const productVariant = productVariants.find(

Custom Checkout Flow without Shipping Steps

Medusa supports customizing the checkout flow based on your use case. Since your ticket product variants don't need to be physically shipped to the customer, the customer doesn't need to enter their shipping address or choose a shipping method.

So, you customize the checkout flow to not require shipping steps by:

  • Disabling the Copy to clipboardrequire_inventory property of a ticket product variant's inventory item.
  • Customizing the checkout flow in the Next.js Starter Storefront to only require a billing address and a payment method.
export default async function CheckoutForm({
cart,
customer,
}) {
const paymentMethods = await listCartPaymentMethods(cart.region?.id ?? "")
return (
<div className="w-full grid grid-cols-1 gap-y-8">
<Addresses cart={cart} customer={customer} />
<Payment cart={cart} availablePaymentMethods={paymentMethods} />
<Review cart={cart} />
</div>
)
}

Send QR Code Tickets in Order Confirmation

After a customer purchases a ticket, you can generate a QR code and send it in the order confirmation email.

import { SubscriberArgs, type SubscriberConfig } from "@medusajs/medusa"
import { TICKET_BOOKING_MODULE } from "../modules/ticket-booking"
export default async function handleOrderPlaced({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
const query = container.resolve("query")
const notificationModuleService = container.resolve("notification")
const ticketBookingModuleService = container.resolve(TICKET_BOOKING_MODULE)
const { data: [order] } = await query.graph({
entity: "order",
fields: [
"id",
"email",
"ticket_purchases.*",
],
filters: {
id: data.id,

You can also add an API route that executes a workflow to validate a ticket. This is useful when scanning QR code tickets at an event's entrance.

import { createWorkflow, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { verifyTicketPurchaseStep } from "./steps/verify-ticket-purchase-step"
import { updateTicketPurchaseStatusStep } from "./steps/update-ticket-purchase-status-step"
export type VerifyTicketPurchaseWorkflowInput = {
ticket_purchase_id: string
}
export const verifyTicketPurchaseWorkflow = createWorkflow(
"verify-ticket-purchase",
function (input: VerifyTicketPurchaseWorkflowInput) {
verifyTicketPurchaseStep(input)
const ticketPurchase = updateTicketPurchaseStatusStep({
ticket_purchase_id: input.ticket_purchase_id,
status: "scanned",
})
return new WorkflowResponse(ticketPurchase)
}

Tutorial: Implement Ticket Booking System

By following this tutorial in our documentation, you'll learn how to:

  • Create a Ticket Booking Module with data models related to selling tickets.
  • Customize the Medusa Admin to manage venues and shows.
  • Implement custom validation and flows for ticket booking.
  • Generate QR codes for tickets and verify them at the venue.
  • Extend the Next.js Starter Storefront to allow booking tickets and choosing seats.

Share this post

Ready to build your custom commerce setup?