---
title: "Ultimate Guide to Sharing Medusa Types in a Monorepo"
slug: "share-medusa-types-monorepo-nextjs"
published: "2026-05-04"
updated: "2026-05-02"
validated: "2026-05-02"
categories:
  - "Next.js"
tags:
  - "Medusa types monorepo"
  - "share Medusa types"
  - "InferTypeOf"
  - "shared-types package"
  - "Next.js storefront"
  - "pnpm workspace monorepo"
  - "TypeScript 5"
  - "medusa InferTypeOf pattern"
  - "shared medusa types"
  - "model type aliases"
  - "DTO interfaces"
  - "typecheck storefront"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "medusa v2"
  - "next.js 15"
  - "typescript 5.x"
  - "pnpm workspace"
  - "payload cms"
status: "stable"
llm-purpose: "Medusa types monorepo: Learn a proven InferTypeOf + shared-types pattern to export stable model aliases so your Next.js storefront typechecks…"
llm-prereqs:
  - "Access to Medusa v2"
  - "Access to Next.js 15"
  - "Access to TypeScript 5.x"
  - "Access to pnpm workspace"
  - "Access to Payload CMS"
llm-outputs:
  - "Completed outcome: Medusa types monorepo: Learn a proven InferTypeOf + shared-types pattern to export stable model aliases so your Next.js storefront typechecks…"
---

**Summary Triples**
- (repository layout, contains, apps/medusa-backend, apps/medusa-storefront, packages/shared-types)
- (shared-types package, exports, inferred Medusa model type aliases created with InferTypeOf)
- (storefront, should import, @repo/shared-types instead of .medusa/types)
- (.medusa generated types, intended for, editor autocompletion only — do not import or commit)
- (InferTypeOf, usage, infer TypeScript types from Medusa model definitions for app-facing contracts)
- (type stability, achieved by, exporting model aliases from actual model definitions into packages/shared-types)
- (validation workflow, uses, backend build + tsc --noEmit in storefront to ensure type correctness)
- (pnpm workspace, enables, package-level imports (e.g., @repo/shared-types) without publishing)
- (custom Medusa fields, handle with, explicit InferTypeOf exports and update shared-types when model definitions change)
- (shared-types package, should provide, type-only exports and/or declaration (.d.ts) files consumable by Next.js storefront)

### {GOAL}
Medusa types monorepo: Learn a proven InferTypeOf + shared-types pattern to export stable model aliases so your Next.js storefront typechecks…

### {PREREQS}
- Access to Medusa v2
- Access to Next.js 15
- Access to TypeScript 5.x
- Access to pnpm workspace
- Access to Payload CMS

### {STEPS}
1. Set up monorepo workspace and packages
2. Import models and use InferTypeOf
3. Export stable model aliases
4. Re-export package root entry points
5. Add dependency to storefront and install
6. Build backend and typecheck storefront

<!-- llm:goal="Medusa types monorepo: Learn a proven InferTypeOf + shared-types pattern to export stable model aliases so your Next.js storefront typechecks…" -->
<!-- llm:prereq="Access to Medusa v2" -->
<!-- llm:prereq="Access to Next.js 15" -->
<!-- llm:prereq="Access to TypeScript 5.x" -->
<!-- llm:prereq="Access to pnpm workspace" -->
<!-- llm:prereq="Access to Payload CMS" -->
<!-- llm:output="Completed outcome: Medusa types monorepo: Learn a proven InferTypeOf + shared-types pattern to export stable model aliases so your Next.js storefront typechecks…" -->

# Ultimate Guide to Sharing Medusa Types in a Monorepo
> Medusa types monorepo: Learn a proven InferTypeOf + shared-types pattern to export stable model aliases so your Next.js storefront typechecks…
Matija Žiberna · 2026-05-04

# How to Share Medusa Types in a Monorepo with a Next.js Storefront

If you are building a Medusa backend inside a monorepo and want your storefront to consume Medusa data types, the default instinct is to look for something auto-generated — the same way Payload CMS generates a shared type file. Medusa does generate types under `.medusa`, but the official docs say those generated types are meant for auto-completion and type checking, should not be imported directly in your code, and should not be committed. For reusable app-facing types, Medusa recommends inferring types from your model definitions with [`InferTypeOf`](https://docs.medusajs.com/resources/references/helper-steps/infer-type-of). The pattern to follow is to export those inferred type aliases from your actual Medusa models through a shared package you maintain yourself.

This guide walks through exactly how we set that up: the folder structure, the `InferTypeOf` pattern, the gotchas we hit, and the recommended update workflow going forward.

**Tested setup**
- pnpm workspace monorepo
- Medusa v2
- Next.js 15
- TypeScript 5.x
- Validated by: successful backend build plus storefront typecheck (`tsc --noEmit`)
- Last reviewed: April 2026

---

## What we were trying to solve

In our monorepo, we have three main packages:

- `apps/medusa-backend` — the Medusa app
- `apps/medusa-storefront` — the Next.js storefront
- `packages/shared-types` — types consumed across apps

The goal was to let the storefront import Medusa-related types from `@repo/shared-types`, avoid importing from `.medusa/types` directly, avoid brittle copied files, and keep the contract stable as the backend evolves.

Getting to a working solution required understanding why the Payload-style approach does not apply here — and building an alternative that fits how Medusa actually works.

---

## Payload CMS vs Medusa: how type generation differs

The comparison matters because if you have both Payload and Medusa in the same monorepo, you need two separate strategies.

| | Payload CMS | Medusa |
|---|---|---|
| Where types are generated | `packages/shared-types/src/generated/payload/payload-types.ts` | `.medusa/types` inside the Medusa app |
| Intended for cross-app import | Yes — it is a shared contract | No. Medusa docs say the generated types are only for auto-completion and type checking and should not be imported directly |
| Automatically available to storefront | Yes, after running `generate:payload-types` | No. If you need a model's type in custom code, Medusa recommends `InferTypeOf<typeof Model>` |

Payload writes its generated types into `packages/shared-types` directly, so they naturally become the source of truth for other apps in the workspace.

Medusa also generates types, but for a different purpose: auto-completion, type checking, Query graph typing (via `query-entry-points.d.ts`), and container module resolution typing (via `modules-bindings.d.ts`). Those generated files are not meant to be imported directly or committed to your repository.

---

## Why `.medusa/types` should not be shared directly

There are a few reasons we ruled out sharing the generated Medusa files.

**Medusa's docs explicitly say not to import them directly.** The generated `.medusa` types are documented as only meant for auto-completion and type checking. You should not import them directly in your code. That alone rules out any approach that shares them across apps.

**The generated files are rebuilt during build or dev, are not meant to be committed, and are not meant to be imported directly.** That makes them a poor fit for a shared cross-app contract, even if they are useful inside the backend for development ergonomics.

**Some generated files exist specifically to type Medusa internals.** `query-entry-points.d.ts` provides typing for Query graph entries. `modules-bindings.d.ts` provides typing for module registration names in the container. These are backend-local by design and are not suitable for sharing.

**Sync scripts create build ordering problems.** A sync script sounds manageable until you have to answer: when does it run, which files are safe to copy, what happens to relative imports inside generated files when they move, and what happens when a consumer builds before the sync runs.

The correct approach skips all of that.

---

## The Medusa-documented pattern: `InferTypeOf` + shared type aliases

The Medusa-documented pattern is to use `InferTypeOf<typeof Model>` when you need a model's type in custom code, then export the aliases your other apps should consume. Applied to a monorepo, that means:

1. Keep `.medusa/types` local to the backend
2. Derive reusable types from actual model definitions using `InferTypeOf`
3. Export those stable aliases from `packages/shared-types`
4. Let the storefront import from `@repo/shared-types`

This gives you a shared contract you control. When a model changes, you update the alias. The storefront stays insulated from backend internals.

---

## Monorepo structure

Here is the relevant structure in our project:

```txt
apps/
  medusa-backend/
    src/modules/
      b2b/
      pickup-scheduling/
      review/
  medusa-storefront/

packages/
  shared-types/
    src/
      generated/
        payload/
          payload-types.ts
      medusa/
        b2b.ts
        pickup-scheduling.ts
        review.ts
        index.ts
      index.ts
```

The `packages/shared-types` directory holds two separate sources of truth: Payload CMS generated types and manually maintained Medusa shared types. That split is intentional. They are generated and maintained differently, so they live separately.

---

## Defining shared Medusa types with `InferTypeOf`

For each Medusa model you want to expose, use `InferTypeOf<typeof Model>` to derive a stable TypeScript type alias from the model definition itself.

Medusa's docs show this exact pattern: import `InferTypeOf` from `@medusajs/framework/types`, import the model itself, and derive a type alias from `typeof Model`. Recipe examples in the docs use the same approach whenever a custom type is needed in code outside the backend core.

```ts
// File: packages/shared-types/src/medusa/b2b.ts
import type { InferTypeOf } from "@medusajs/framework/types"
import { Company } from "../../../../apps/medusa-backend/src/modules/b2b/models/company"
import { CompanyUser } from "../../../../apps/medusa-backend/src/modules/b2b/models/company-user"
import { QuoteRequest } from "../../../../apps/medusa-backend/src/modules/b2b/models/quote-request"

export type B2BCompany = InferTypeOf<typeof Company>
export type B2BCompanyUser = InferTypeOf<typeof CompanyUser>
export type B2BQuoteRequest = InferTypeOf<typeof QuoteRequest>
```

`InferTypeOf` reads the model definition and produces a TypeScript type that reflects its shape. Because you are importing from the actual model files — not from generated artifacts — this works regardless of what `.medusa/types` contains.

Repeat the same pattern for each feature module:

```ts
// File: packages/shared-types/src/medusa/pickup-scheduling.ts
import type { InferTypeOf } from "@medusajs/framework/types"
import { PickupSlot } from "../../../../apps/medusa-backend/src/modules/pickup-scheduling/models/pickup-slot"
import { PickupSelection } from "../../../../apps/medusa-backend/src/modules/pickup-scheduling/models/pickup-selection"

export type MedusaPickupSlot = InferTypeOf<typeof PickupSlot>
export type MedusaPickupSelection = InferTypeOf<typeof PickupSelection>

// Input types for custom operations can be plain interfaces
export type CreatePickupSlotInput = {
  date: string
  capacity: number
}
```

For input types and DTOs that do not map directly to a model, plain TypeScript interfaces are the right choice. These give you the same cross-app stability without pulling in any Medusa internals.

---

## Wiring the shared package

Re-export everything from a single Medusa index:

```ts
// File: packages/shared-types/src/medusa/index.ts
export type * from "./b2b"
export type * from "./pickup-scheduling"
export type * from "./review"
```

Then re-export both Payload and Medusa types from the package root:

```ts
// File: packages/shared-types/src/index.ts
export * from "./generated/payload/payload-types"
export type * from "./medusa"
```

This lets the storefront import Payload types and Medusa types from the same entry point without knowing where each originates.

---

## Consuming shared types in the storefront

Once the shared package is wired correctly, the storefront imports are clean:

```ts
// File: apps/medusa-storefront/src/lib/types.ts
import type {
  B2BCompany,
  B2BQuoteRequest,
  MedusaPickupSlot,
  MedusaReview,
  Post,
  Page,
} from "@repo/shared-types"
```

`Post` and `Page` come from Payload. The Medusa types come from your manually maintained aliases. The storefront does not need to know the difference.

---

## Gotchas and what breaks without them

### 1. The storefront must declare `@repo/shared-types` as a dependency

TypeScript resolution will fail if the consumer app does not explicitly declare the dependency, even if it is accessible in the workspace:

```json
// File: apps/medusa-storefront/package.json
{
  "dependencies": {
    "@repo/shared-types": "workspace:*"
  }
}
```

Run `pnpm install` after adding it. Errors from a missing workspace dependency often look unrelated, which makes this easy to overlook.

### 2. Relative import paths from `packages/shared-types` to backend models go deeper than expected

The shared Medusa type files live at:

```txt
packages/shared-types/src/medusa/
```

The backend models live at:

```txt
apps/medusa-backend/src/modules/
```

That gap requires going up four directory levels from the type file before descending into `apps/`. Getting this wrong produces silent resolution failures that are confusing to diagnose.

### 3. If `packages/shared-types` re-exports Payload types, the generated Payload file must exist

The package root re-exports:

```ts
export * from "./generated/payload/payload-types"
```

If that file is missing — for example after a fresh clone or a clean build — the entire shared package fails to typecheck, which takes down any app that depends on it. After any major setup change, run:

```bash
pnpm generate:payload-types
```

### 4. Do not over-share when the storefront only needs a narrow shape

`InferTypeOf` exposes the full model shape. When the storefront only needs a handful of fields, export a DTO interface instead:

```ts
// File: packages/shared-types/src/medusa/b2b.ts
export type B2BCompanySummary = {
  id: string
  name: string
  slug: string
  status: "active" | "inactive"
}
```

Narrower types reduce accidental coupling between the storefront and backend internals.

---

## Recommended update workflow

When you add or change a Medusa model and another app needs that type:

1. Update the Medusa model in `apps/medusa-backend/src/modules/...`
2. Update or add the shared alias in `packages/shared-types/src/medusa/...`
3. Re-export it from `packages/shared-types/src/medusa/index.ts`
4. Add a DTO instead of the full inferred model if the storefront only needs a subset
5. Run:

```bash
pnpm build:backend
pnpm generate:payload-types
```

6. Typecheck the consumer app:

```bash
pnpm --filter medusa-storefront exec tsc -p tsconfig.json --noEmit
```

---

## FAQ

**Can I just copy `.medusa/types` files into `packages/shared-types` with a script?**

You can, but you should not. Generated Medusa files include backend-local references that are not portable, and the file shapes can change without warning when your module graph changes. The `InferTypeOf` pattern gives you a stable contract without the maintenance risk.

**Do I need to re-run anything when a Medusa model changes?**

Yes. After changing a model, update the corresponding alias in `packages/shared-types/src/medusa/`, rebuild the backend, and run a typecheck on the storefront. The alias does not auto-update — that is a deliberate tradeoff for stability.

**What if the storefront needs a type that is not a direct model shape?**

Define a plain TypeScript interface in the shared Medusa type files. You are not restricted to `InferTypeOf` — it is just the right tool when the shape comes directly from a model. Input types, status enums, and computed shapes belong as plain interfaces.

**Should the shared types package depend on `@medusajs/framework`?**

It will need to, because `InferTypeOf` is imported from `@medusajs/framework/types`. Add it as a dev dependency or peer dependency in `packages/shared-types/package.json`.

**Is this approach aligned with Medusa's official recommendations?**

This approach aligns with Medusa's documentation and recipe examples, which use `InferTypeOf` against model definitions when a custom type is needed. The docs explicitly discourage importing generated `.medusa` types directly, so deriving aliases from model definitions is the documented path.

---

## Conclusion

Sharing Medusa types across a monorepo requires a different mental model than Payload CMS. Payload generates a shared contract automatically. Medusa generates local backend helpers that are documented as not meant for direct import. For cross-app type sharing in Medusa, the documented approach is to maintain a stable contract yourself: import from actual model definitions using `InferTypeOf`, export stable aliases from `packages/shared-types`, and let the storefront depend on that package alone.

The approach is explicit, resilient to backend changes, and matches how Medusa expects its type system to be used. The storefront stays clean, the backend stays encapsulated, and the shared contract stays under your control.

If you have questions about the setup or ran into a different gotcha, leave a comment below. And if you are building on Medusa in a monorepo, the newsletter covers practical guides like this one regularly.

---

**Written by Matija Žiberna**
Developer working with Medusa, Next.js, Payload CMS, and monorepo architectures. I build production e-commerce and multi-tenant systems and write about what I run into.
[Author page →](https://www.buildwithmatija.com/about)

Published: April 2026 · Last updated: April 2026

Thanks,
Matija

## LLM Response Snippet
```json
{
  "goal": "Medusa types monorepo: Learn a proven InferTypeOf + shared-types pattern to export stable model aliases so your Next.js storefront typechecks…",
  "responses": [
    {
      "question": "What does the article \"Ultimate Guide to Sharing Medusa Types in a Monorepo\" cover?",
      "answer": "Medusa types monorepo: Learn a proven InferTypeOf + shared-types pattern to export stable model aliases so your Next.js storefront typechecks…"
    }
  ]
}
```