---
title: "Share Medusa Types in a Monorepo: Stable Next.js Setup"
slug: "share-medusa-types-monorepo-storefront"
published: "2026-04-29"
updated: "2026-05-02"
categories:
  - "Medusa.js"
tags:
  - "share Medusa types"
  - "Medusa types monorepo"
  - "packages/shared-types"
  - "InferTypeOf"
  - "Next.js storefront"
  - "monorepo TypeScript types"
  - "Payload CMS types"
  - "do not import .medusa/types"
  - "DTO for storefront"
  - "type sharing Medusa"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "next.js@15"
  - "typescript@5"
  - "pnpm@8"
  - "medusa@latest"
  - "payload@latest"
status: "stable"
llm-purpose: "Share Medusa types in a monorepo: export stable model aliases from packages/shared-types with InferTypeOf to power your Next.js storefront. Follow the…"
llm-prereqs:
  - "Access to Medusa"
  - "Access to Next.js"
  - "Access to TypeScript"
  - "Access to pnpm"
  - "Access to Payload CMS"
llm-outputs:
  - "Completed outcome: Share Medusa types in a monorepo: export stable model aliases from packages/shared-types with InferTypeOf to power your Next.js storefront. Follow the…"
---

**Summary Triples**
- (Medusa generated types, are located at, apps/medusa-backend/.medusa/types (not intended for cross-app imports))
- (Payload CMS generated types, can be written to, packages/shared-types/src/generated/payload/payload-types.ts (suitable as workspace contract))
- (Storefront, should import Medusa-related types from, @repo/shared-types (packages/shared-types))
- (Correct approach, is to, export stable model aliases in packages/shared-types using InferTypeOf, not import .medusa/types)
- (Stable contract, is maintained by, authoring explicit type aliases/DTOs in shared-types and referencing them from the storefront)
- (Monorepo TypeScript, requires, tsconfig path mappings or workspace package references so apps resolve @repo/shared-types)

### {GOAL}
Share Medusa types in a monorepo: export stable model aliases from packages/shared-types with InferTypeOf to power your Next.js storefront. Follow the…

### {PREREQS}
- Access to Medusa
- Access to Next.js
- Access to TypeScript
- Access to pnpm
- Access to Payload CMS

### {STEPS}
1. Understand Medusa vs Payload types
2. Create packages/shared-types package
3. Define inferred aliases with InferTypeOf
4. Group and re-export feature types
5. Add shared-types as storefront dependency
6. Regenerate Payload types and build
7. Typecheck consumer and iterate

<!-- llm:goal="Share Medusa types in a monorepo: export stable model aliases from packages/shared-types with InferTypeOf to power your Next.js storefront. Follow the…" -->
<!-- llm:prereq="Access to Medusa" -->
<!-- llm:prereq="Access to Next.js" -->
<!-- llm:prereq="Access to TypeScript" -->
<!-- llm:prereq="Access to pnpm" -->
<!-- llm:prereq="Access to Payload CMS" -->
<!-- llm:output="Completed outcome: Share Medusa types in a monorepo: export stable model aliases from packages/shared-types with InferTypeOf to power your Next.js storefront. Follow the…" -->

# Share Medusa Types in a Monorepo: Stable Next.js Setup
> Share Medusa types in a monorepo: export stable model aliases from packages/shared-types with InferTypeOf to power your Next.js storefront. Follow the…
Matija Žiberna · 2026-04-29

# How to Share Medusa Types in a Monorepo with a Storefront

If you are building a Medusa backend inside a monorepo and want your storefront to understand your Medusa data types, the first assumption is usually wrong:

Medusa does **not** give you a shared generated type contract in the same way that a system like Payload CMS does.

That distinction matters a lot.

In our monorepo, we have:

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

The goal was simple:

- let the storefront import Medusa-related types from `@repo/shared-types`
- avoid importing from `.medusa`
- avoid brittle copied generated files
- keep the contract stable when the backend evolves

This guide explains the approach we ended up with, why it is the correct Medusa-style solution, and the gotchas we hit along the way.

## The key difference: Payload CMS vs Medusa

Payload CMS and Medusa solve type generation differently.

### Payload CMS

Payload can generate a shared TypeScript contract directly from your config. In our monorepo, Payload writes generated types to:

```txt
packages/shared-types/src/generated/payload/payload-types.ts
```

That means Payload-generated types can naturally become the source of truth for other apps in the workspace.

### Medusa

Medusa also generates types, but not for the same purpose.

When you run Medusa in `dev` or `build`, it generates files under:

```txt
apps/medusa-backend/.medusa/types
```

These generated types are useful for:

- local autocomplete
- local type checking
- Query graph typing
- container module resolution typing

They are **not** intended to be imported as a cross-app contract.

That is the important distinction.

## Why you should not share `.medusa/types` directly

At first, it is tempting to do one of these:

1. import from `apps/medusa-backend/.medusa/types/...` in the storefront
2. copy the generated `.d.ts` files into `packages/shared-types`
3. build a sync script that mirrors `.medusa/types` into another package

We explored that path and rejected it.

Here is why.

### 1. Medusa explicitly does not want you importing generated types directly

Medusa's own guidance is that generated types are for development assistance, not for direct imports in app code.

That alone is a strong signal that `.medusa/types` should remain local.

### 2. Generated Medusa files are not a stable public API

They are derived implementation artifacts. If your module graph changes, your generated files can change shape as well.

That is fine for backend-local autocomplete. It is not a good contract for a storefront or another app.

### 3. Some generated files are backend-local by nature

For example, `modules-bindings.d.ts` can reference backend-local modules and service registrations. That makes it unsuitable for generic sharing.

### 4. Copying generated files introduces maintenance overhead

A sync step sounds simple until it is not:

- when should it run?
- which files are safe to copy?
- what happens when relative imports inside generated files stop making sense?
- what happens if consumers build before sync runs?

If your goal is a stable shared contract, syncing generated Medusa files is solving the wrong problem.

## The right pattern: export stable inferred types from `packages/shared-types`

The Medusa-friendly approach is:

1. keep `.medusa/types` local to the backend
2. derive reusable types from the actual model definitions with `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.

## Monorepo structure

Here is the relevant structure:

```txt
apps/
  medusa-backend/
    src/modules/
      b2b/
      pickup-scheduling/
      review/
  medusa-storefront/
packages/
  shared-types/
    src/
      generated/
        payload/
      medusa/
        b2b.ts
        pickup-scheduling.ts
        review.ts
        index.ts
      index.ts
```

The important point is that `packages/shared-types` contains **two different sources of truth**:

- generated Payload CMS types
- manually maintained Medusa shared types

That split is intentional.

## How to define shared Medusa types

For Medusa models, use `InferTypeOf<typeof Model>`.

Example pattern:

```ts
import type { InferTypeOf } from "@medusajs/framework/types"
import { Company } from "../../../../apps/medusa-backend/src/modules/b2b/models/company"

export type B2BCompany = InferTypeOf<typeof Company>
```

In our repo, we grouped those by feature:

### `packages/shared-types/src/medusa/b2b.ts`

This exports aliases such as:

- `B2BCompany`
- `B2BCompanyUser`
- `B2BQuoteRequest`
- `B2BQuoteRequestItem`
- `B2BApprovalRule`
- `B2BContractTerms`
- `B2BCustomerGroup`
- `B2BPaymentTerm`
- `B2BCartContext`
- `B2BOrderContext`

### `packages/shared-types/src/medusa/pickup-scheduling.ts`

This exports:

- `MedusaPickupSlot`
- `MedusaPickupSelection`
- `CreatePickupSlotInput`
- `SetPickupSelectionInput`
- `ConfirmOrderPickupSelectionInput`
- `PickupSelectionStatus`

### `packages/shared-types/src/medusa/review.ts`

This exports:

- `MedusaReview`

Then re-export everything from:

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

And finally from the shared package root:

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

That lets the storefront import both Payload and Medusa types from one place.

## How the storefront consumes them

Once the shared package is wired correctly, your storefront can do this:

```ts
import type {
  B2BCompany,
  B2BQuoteRequest,
  MedusaPickupSlot,
  MedusaReview,
  Post,
  Page,
} from "@repo/shared-types"
```

This is much better than importing from `.medusa`, because:

- the contract is stable
- the storefront only depends on your shared package
- you control which Medusa shapes are exposed
- you can evolve the backend without leaking implementation details

## The gotchas we found

This is where most implementations go wrong.

### Gotcha 1: Medusa does not auto-generate shared monorepo types like Payload does

This was the biggest misunderstanding.

Payload can generate a shared file into `packages/shared-types`.

Medusa does not do that.

So if you want a shared contract for the storefront, you must maintain it yourself.

### Gotcha 2: do not build a sync flow around `.medusa/types`

We considered a `sync-types.sh` approach that copied generated Medusa declarations into `packages/shared-types`.

We removed it.

Why?

- it fights Medusa's intended usage
- it is easy to copy files that are not portable
- it creates unnecessary build ordering issues
- it still does not give you a stable public contract

If you are searching for **how to share Medusa types in a monorepo with a Next.js storefront**, the answer is not "copy `.medusa/types` into another package."

The answer is "export inferred aliases from your actual Medusa models."

### Gotcha 3: your consumer app must actually depend on `@repo/shared-types`

The storefront in our workspace re-exported from `@repo/shared-types`, but TypeScript still needs that package declared as a workspace dependency.

Add it to the consumer app:

```json
{
  "dependencies": {
    "@repo/shared-types": "workspace:*"
  }
}
```

Then run:

```bash
pnpm install
```

Without that, type resolution may fail in ways that look unrelated.

### Gotcha 4: relative import paths from `packages/shared-types` to backend models are easy to get wrong

Our shared Medusa type files live under:

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

The backend models live under:

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

That means the relative path is deeper than it first looks.

A broken path here will make the storefront fail to resolve the shared package.

This is another reason why keeping the shared contract explicit is better than trying to mirror generated backend artifacts.

### Gotcha 5: if `packages/shared-types` also re-exports Payload generated types, that generated file must exist

Our root shared-types entrypoint re-exports:

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

So after changing shared-type wiring, we also had to regenerate the Payload types:

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

If that file is missing, the whole shared package fails to typecheck.

### Gotcha 6: do not over-share if the storefront only needs a smaller shape

`InferTypeOf` is great when the storefront genuinely needs the backend model shape.

But sometimes the storefront only needs a narrowed version such as:

- id
- slug
- title
- status

In those cases, define a DTO instead of exporting the whole inferred model type.

That keeps your cross-app contract cleaner and reduces accidental coupling.

## Recommended workflow for future updates

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. If needed, add a DTO instead of exposing the full inferred model.
5. Run:

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

6. Typecheck the consumer app.

In our case, this was enough:

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

## Rule of thumb

If you remember only one thing, make it this:

- Payload CMS types are generated and shared automatically
- Medusa generated `.medusa/types` are local only
- if you want to share Medusa types in a monorepo with a storefront, create a stable contract in `packages/shared-types`
- use `InferTypeOf` for model-backed aliases
- never import `.medusa/types` across apps

## Final recommendation

If you are trying to **share Medusa model types with a Next.js storefront in a monorepo**, treat Medusa and Payload as two different systems:

- let Payload keep auto-generating its shared types
- let Medusa keep generating local backend-only helper types
- build your storefront-facing Medusa contract manually in `packages/shared-types`

That approach is simple, explicit, and resilient.

More importantly, it matches how Medusa expects its generated types to be used.

## LLM Response Snippet
```json
{
  "goal": "Share Medusa types in a monorepo: export stable model aliases from packages/shared-types with InferTypeOf to power your Next.js storefront. Follow the…",
  "responses": [
    {
      "question": "What does the article \"Share Medusa Types in a Monorepo: Stable Next.js Setup\" cover?",
      "answer": "Share Medusa types in a monorepo: export stable model aliases from packages/shared-types with InferTypeOf to power your Next.js storefront. Follow the…"
    }
  ]
}
```