> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/medusajs/medusa/llms.txt
> Use this file to discover all available pages before exploring further.

# Fulfillment Providers

> Learn how to configure and use fulfillment providers in Medusa for managing order fulfillment and shipping.

# Fulfillment Providers

Fulfillment providers handle shipping, delivery, and order fulfillment operations. They calculate shipping prices, validate fulfillment data, create shipments, and manage returns.

## Available Fulfillment Providers

### Manual Fulfillment Provider

The manual fulfillment provider (`@medusajs/medusa/fulfillment-manual`) is a simple provider for self-managed fulfillment where you handle shipping logistics yourself.

**Features:**

* No external shipping integrations
* Manual fulfillment process
* Supports both regular fulfillment and returns
* Does not calculate shipping prices

## Installation

The manual fulfillment provider is included in the core Medusa package:

```bash theme={null}
npm install @medusajs/medusa
```

## Configuration

Configure the fulfillment provider in your `medusa-config.ts`:

```typescript theme={null}
import { defineConfig } from "@medusajs/framework/utils"

export default defineConfig({
  modules: [
    {
      resolve: "@medusajs/medusa/fulfillment",
      options: {
        providers: [
          {
            resolve: "@medusajs/medusa/fulfillment-manual",
            id: "manual",
          },
        ],
      },
    },
  ],
})
```

## Fulfillment Provider Interface

All fulfillment providers extend `AbstractFulfillmentProviderService` and implement the following methods:

### `getFulfillmentOptions(): Promise<FulfillmentOption[]>`

Returns available fulfillment options.

```typescript theme={null}
const options = await fulfillmentProvider.getFulfillmentOptions()
// Result:
// [
//   { id: "manual-fulfillment" },
//   { id: "manual-fulfillment-return", is_return: true }
// ]
```

### `validateFulfillmentData(optionData, data, context): Promise<any>`

Validates fulfillment data before creating a fulfillment.

**Parameters:**

* `optionData` - Configuration data for the fulfillment option
* `data` - Fulfillment data to validate
* `context` - Additional context including cart/order data

```typescript theme={null}
const validatedData = await fulfillmentProvider.validateFulfillmentData(
  { option_id: "manual-fulfillment" },
  {
    tracking_number: "TRACK123",
  },
  {
    cart_id: "cart_123",
  }
)
```

### `validateOption(data): Promise<boolean>`

Validates a fulfillment option configuration.

```typescript theme={null}
const isValid = await fulfillmentProvider.validateOption({
  id: "manual-fulfillment",
})
```

### `canCalculate(): Promise<boolean>`

Indicates whether the provider can calculate shipping prices.

```typescript theme={null}
const canCalculate = await fulfillmentProvider.canCalculate()
// false for manual provider
```

### `calculatePrice(optionData, data, context): Promise<CalculatedShippingOptionPrice>`

Calculates shipping price (not supported by manual provider).

**Parameters:**

* `optionData` - Shipping option configuration
* `data` - Calculation data (items, destination, etc.)
* `context` - Additional context

<Warning>
  The manual fulfillment provider throws an error when `calculatePrice` is called,
  as it does not support price calculation.
</Warning>

### `createFulfillment(): Promise<CreateFulfillmentResult>`

Creates a fulfillment (shipment).

```typescript theme={null}
const result = await fulfillmentProvider.createFulfillment()
// Result: { data: {}, labels: [] }
```

**Returns:**

* `data` - Provider-specific fulfillment data
* `labels` - Shipping labels (if applicable)

### `cancelFulfillment(): Promise<any>`

Cancels an existing fulfillment.

```typescript theme={null}
await fulfillmentProvider.cancelFulfillment()
```

### `createReturnFulfillment(): Promise<CreateFulfillmentResult>`

Creates a return fulfillment.

```typescript theme={null}
const result = await fulfillmentProvider.createReturnFulfillment()
// Result: { data: {}, labels: [] }
```

## Using the Fulfillment Module

Access fulfillment providers through the Fulfillment Module:

```typescript theme={null}
import { Modules } from "@medusajs/framework/utils"

const fulfillmentModule = container.resolve(Modules.FULFILLMENT)

// Get fulfillment options
const options = await fulfillmentModule.listFulfillmentOptions(
  "manual"
)

// Create a fulfillment
const fulfillment = await fulfillmentModule.createFulfillment({
  provider_id: "manual",
  order_id: "order_123",
  items: [
    {
      line_item_id: "item_123",
      quantity: 1,
    },
  ],
  labels: [],
  data: {
    tracking_number: "TRACK123",
  },
})
```

## Fulfillment Workflows

Use fulfillment workflows from `@medusajs/core-flows`:

```typescript theme={null}
import { createFulfillmentWorkflow } from "@medusajs/core-flows"

const { result } = await createFulfillmentWorkflow(container).run({
  input: {
    order_id: "order_123",
    provider_id: "manual",
    items: [
      {
        line_item_id: "item_123",
        quantity: 1,
      },
    ],
  },
})
```

## Creating Custom Fulfillment Providers

Create a custom fulfillment provider by extending `AbstractFulfillmentProviderService`:

```typescript packages/modules/providers/fulfillment-custom/src/services/custom-fulfillment.ts theme={null}
import { AbstractFulfillmentProviderService } from "@medusajs/framework/utils"
import {
  CalculatedShippingOptionPrice,
  CalculateShippingOptionPriceContext,
  CreateFulfillmentResult,
  FulfillmentOption,
  ValidateFulfillmentDataContext,
} from "@medusajs/types"

export class CustomFulfillmentService extends AbstractFulfillmentProviderService {
  static identifier = "custom-fulfillment"

  constructor(container, options) {
    super()
    this.options_ = options
    // Initialize shipping provider SDK
    this.client_ = new ShippingProviderSDK(options.apiKey)
  }

  async getFulfillmentOptions(): Promise<FulfillmentOption[]> {
    // Return available shipping options
    return [
      {
        id: "standard-shipping",
      },
      {
        id: "express-shipping",
      },
      {
        id: "return-shipping",
        is_return: true,
      },
    ]
  }

  async validateFulfillmentData(
    optionData: Record<string, unknown>,
    data: Record<string, unknown>,
    context: ValidateFulfillmentDataContext
  ): Promise<any> {
    // Validate shipping address, items, etc.
    if (!context.shipping_address) {
      throw new Error("Shipping address is required")
    }

    return data
  }

  async validateOption(data: Record<string, any>): Promise<boolean> {
    // Validate the option configuration
    const validOptions = ["standard-shipping", "express-shipping"]
    return validOptions.includes(data.id)
  }

  async canCalculate(): Promise<boolean> {
    return true
  }

  async calculatePrice(
    optionData: Record<string, unknown>,
    data: Record<string, unknown>,
    context: CalculateShippingOptionPriceContext
  ): Promise<CalculatedShippingOptionPrice> {
    // Calculate shipping price from provider
    const rate = await this.client_.getRates({
      from: context.from_address,
      to: context.shipping_address,
      items: context.items,
      service: optionData.id,
    })

    return {
      calculated_amount: rate.amount,
    }
  }

  async createFulfillment(
    data: Record<string, unknown>,
    items: any[],
    order: any,
    fulfillment: any
  ): Promise<CreateFulfillmentResult> {
    // Create shipment with provider
    const shipment = await this.client_.createShipment({
      order_id: order.id,
      items: items,
      from_address: order.warehouse_address,
      to_address: order.shipping_address,
      service: data.option_id,
    })

    return {
      data: {
        shipment_id: shipment.id,
        tracking_number: shipment.tracking_number,
      },
      labels: [
        {
          url: shipment.label_url,
          format: "pdf",
        },
      ],
    }
  }

  async cancelFulfillment(fulfillment: any): Promise<any> {
    // Cancel shipment with provider
    await this.client_.cancelShipment(fulfillment.data.shipment_id)
    return {}
  }

  async createReturnFulfillment(
    fulfillment: any
  ): Promise<CreateFulfillmentResult> {
    // Create return shipment
    const returnShipment = await this.client_.createReturnShipment({
      original_shipment_id: fulfillment.data.shipment_id,
    })

    return {
      data: {
        shipment_id: returnShipment.id,
        tracking_number: returnShipment.tracking_number,
      },
      labels: [
        {
          url: returnShipment.label_url,
          format: "pdf",
        },
      ],
    }
  }
}
```

Register your custom provider:

```typescript packages/modules/providers/fulfillment-custom/src/index.ts theme={null}
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
import { CustomFulfillmentService } from "./services/custom-fulfillment"

export default ModuleProvider(Modules.FULFILLMENT, {
  services: [CustomFulfillmentService],
})
```

Configure your custom provider:

```typescript medusa-config.ts theme={null}
import { defineConfig } from "@medusajs/framework/utils"

export default defineConfig({
  modules: [
    {
      resolve: "@medusajs/medusa/fulfillment",
      options: {
        providers: [
          {
            resolve: "./src/modules/providers/fulfillment-custom",
            id: "custom-fulfillment",
            options: {
              apiKey: process.env.SHIPPING_PROVIDER_API_KEY,
            },
          },
        ],
      },
    },
  ],
})
```

## Reference

* Source: `packages/modules/providers/fulfillment-manual/src/services/manual-fulfillment.ts`
* Base class: `packages/core/utils/src/fulfillment/abstract-fulfillment-provider.ts`
* Types: `packages/core/types/src/fulfillment/provider.ts`

## Next Steps

<CardGroup cols={2}>
  <Card title="Payment Providers" href="/providers/payment" icon="credit-card">
    Configure payment processing
  </Card>

  <Card title="Notification Providers" href="/providers/notification" icon="bell">
    Send shipping confirmation emails
  </Card>
</CardGroup>
