September 17, 2025·Product
Implement Booking System in Medusa
Shahed Nasser

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

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
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.

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.
1234567891011121314151617181920import { 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.itemsconst 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 cartfor (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 clipboard
require_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.
1234567891011121314151617export 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.
1234567891011121314151617181920import { 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.
1234567891011121314151617181920import { 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.