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

# File Storage Providers

> Learn how to configure and use file storage providers in Medusa for handling file uploads and downloads.

# File Storage Providers

File storage providers handle file uploads, downloads, and storage management. They support both public files (accessible via URL) and private files (requiring presigned URLs).

## Available File Storage Providers

Medusa includes two file storage providers:

### Local File Provider

`@medusajs/medusa/file-local` - Stores files on the local filesystem.

**Use cases:**

* Development and testing
* Local deployments
* Simple file storage needs

**Limitations:**

* Not suitable for production with multiple servers
* No true private file support (files are in static directory)
* Limited scalability

### S3 File Provider

`@medusajs/medusa/file-s3` - Stores files in Amazon S3 or S3-compatible storage.

**Use cases:**

* Production deployments
* Scalable cloud storage
* Multi-server environments
* CDN integration

**Features:**

* True public/private file support
* Presigned URLs for secure access
* Multiple authentication methods (access keys or IAM roles)
* Compatible with S3-compatible services (MinIO, DigitalOcean Spaces, etc.)

## Installation

Both providers are included in the core Medusa package:

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

For S3, also install the AWS SDK:

```bash theme={null}
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
```

## Configuration

<Tabs>
  <Tab title="Local File">
    Configure the local file provider:

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

    export default defineConfig({
    modules: [
    {
      resolve: "@medusajs/medusa/file",
      options: {
        providers: [
          {
            resolve: "@medusajs/medusa/file-local",
            id: "local",
            options: {
              upload_dir: "static",
              backend_url: "http://localhost:9000/static",
            },
          },
        ],
      },
    },
    ],
    })
    ```

    **Options:**

    * `upload_dir` - Directory for storing files (default: `static`)
    * `private_upload_dir` - Directory for private files (default: `static`)
    * `backend_url` - Base URL for accessing files (default: `http://localhost:9000/static`)
  </Tab>

  <Tab title="S3 (Access Keys)">
    Configure S3 with access key authentication:

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

    export default defineConfig({
    modules: [
    {
      resolve: "@medusajs/medusa/file",
      options: {
        providers: [
          {
            resolve: "@medusajs/medusa/file-s3",
            id: "s3",
            options: {
              file_url: process.env.S3_FILE_URL,
              access_key_id: process.env.S3_ACCESS_KEY_ID,
              secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
              region: process.env.S3_REGION,
              bucket: process.env.S3_BUCKET,
              prefix: "medusa",
              cache_control: "public, max-age=31536000",
            },
          },
        ],
      },
    },
    ],
    })
    ```

    Environment variables:

    ```bash .env theme={null}
    S3_FILE_URL=https://your-bucket.s3.amazonaws.com
    S3_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
    S3_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    S3_REGION=us-east-1
    S3_BUCKET=medusa-files
    ```
  </Tab>

  <Tab title="S3 (IAM Role)">
    Configure S3 with IAM role authentication (for EC2, ECS, Lambda):

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

    export default defineConfig({
    modules: [
    {
      resolve: "@medusajs/medusa/file",
      options: {
        providers: [
          {
            resolve: "@medusajs/medusa/file-s3",
            id: "s3",
            options: {
              file_url: process.env.S3_FILE_URL,
              authentication_method: "s3-iam-role",
              region: process.env.S3_REGION,
              bucket: process.env.S3_BUCKET,
              prefix: "medusa",
            },
          },
        ],
      },
    },
    ],
    })
    ```

    <Note>
      With IAM role authentication, credentials are automatically retrieved from the
      environment. Ensure your EC2/ECS instance has the appropriate IAM role attached.
    </Note>
  </Tab>

  <Tab title="S3-Compatible">
    Configure S3-compatible storage (MinIO, DigitalOcean Spaces, etc.):

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

    export default defineConfig({
    modules: [
    {
      resolve: "@medusajs/medusa/file",
      options: {
        providers: [
          {
            resolve: "@medusajs/medusa/file-s3",
            id: "s3",
            options: {
              file_url: process.env.S3_FILE_URL,
              access_key_id: process.env.S3_ACCESS_KEY_ID,
              secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
              region: process.env.S3_REGION,
              bucket: process.env.S3_BUCKET,
              endpoint: process.env.S3_ENDPOINT,
              additional_client_config: {
                forcePathStyle: true,
              },
            },
          },
        ],
      },
    },
    ],
    })
    ```

    For DigitalOcean Spaces:

    ```bash .env theme={null}
    S3_FILE_URL=https://your-space.nyc3.digitaloceanspaces.com
    S3_ENDPOINT=https://nyc3.digitaloceanspaces.com
    S3_REGION=nyc3
    S3_BUCKET=your-space
    ```
  </Tab>
</Tabs>

## File Provider Interface

All file providers extend `AbstractFileProviderService` and implement the following methods:

### `upload(file: ProviderUploadFileDTO): Promise<ProviderFileResultDTO>`

Uploads a file.

```typescript theme={null}
const result = await fileProvider.upload({
  filename: "product-image.jpg",
  mimeType: "image/jpeg",
  content: base64EncodedContent,
  access: "public", // or "private"
})
// Result: { url: "https://...", key: "product-image-123.jpg" }
```

**Parameters:**

* `filename` - Original filename
* `mimeType` - MIME type of the file
* `content` - File content (base64 or UTF-8 string)
* `access` - `"public"` or `"private"`

**Returns:**

* `url` - Public URL (for public files) or identifier
* `key` - File key/identifier for future operations

### `getUploadStream(fileData: ProviderUploadStreamDTO): Promise<UploadStreamResult>`

Gets a writable stream for uploading large files.

```typescript theme={null}
const { writeStream, promise, url, fileKey } =
  await fileProvider.getUploadStream({
    filename: "large-video.mp4",
    mimeType: "video/mp4",
    access: "public",
  })

// Pipe file to the stream
readableStream.pipe(writeStream)

// Wait for upload to complete
const result = await promise
```

### `delete(files: ProviderDeleteFileDTO | ProviderDeleteFileDTO[]): Promise<void>`

Deletes one or more files.

```typescript theme={null}
await fileProvider.delete({
  fileKey: "product-image-123.jpg",
})

// Or delete multiple files
await fileProvider.delete([
  { fileKey: "file1.jpg" },
  { fileKey: "file2.jpg" },
])
```

### `getDownloadStream(file: ProviderGetFileDTO): Promise<Readable>`

Gets a readable stream for downloading a file.

```typescript theme={null}
const stream = await fileProvider.getDownloadStream({
  fileKey: "product-image-123.jpg",
})

// Pipe to response
stream.pipe(res)
```

### `getAsBuffer(file: ProviderGetFileDTO): Promise<Buffer>`

Gets file content as a buffer.

```typescript theme={null}
const buffer = await fileProvider.getAsBuffer({
  fileKey: "product-image-123.jpg",
})
```

### `getPresignedDownloadUrl(file: ProviderGetFileDTO): Promise<string>`

Generates a presigned URL for downloading a private file.

```typescript theme={null}
const url = await fileProvider.getPresignedDownloadUrl({
  fileKey: "private-document-123.pdf",
})
// Result: "https://...?signature=..."
```

<Note>
  Presigned URLs are temporary and expire after a configured duration
  (default: 1 hour for S3).
</Note>

### `getPresignedUploadUrl(fileData: ProviderGetPresignedUploadUrlDTO): Promise<ProviderFileResultDTO>`

Generates a presigned URL for client-side uploads.

```typescript theme={null}
const { url, key } = await fileProvider.getPresignedUploadUrl({
  filename: "user-avatar.jpg",
  mimeType: "image/jpeg",
  access: "public",
  expiresIn: 3600, // 1 hour
})

// Client can now upload directly to this URL
```

## Using the File Module

Access file providers through the File Module:

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

const fileModule = container.resolve(Modules.FILE)

// Upload a file
const file = await fileModule.createFiles({
  filename: "product-image.jpg",
  mimeType: "image/jpeg",
  content: base64Content,
  access: "public",
})

// Get download URL for private file
const url = await fileModule.getPresignedDownloadUrl(file.key)

// Delete a file
await fileModule.deleteFiles(file.id)
```

## Creating Custom File Providers

Create a custom file provider by extending `AbstractFileProviderService`:

```typescript packages/modules/providers/file-custom/src/services/custom-file.ts theme={null}
import { AbstractFileProviderService } from "@medusajs/framework/utils"
import { FileTypes } from "@medusajs/framework/types"
import { Readable } from "stream"

export class CustomFileService extends AbstractFileProviderService {
  static identifier = "custom-file"

  constructor(container, options) {
    super()
    this.options_ = options
    // Initialize storage client
    this.client_ = new StorageClient(options)
  }

  async upload(
    file: FileTypes.ProviderUploadFileDTO
  ): Promise<FileTypes.ProviderFileResultDTO> {
    // Upload file to your storage service
    const result = await this.client_.upload({
      filename: file.filename,
      content: Buffer.from(file.content, "base64"),
      acl: file.access === "public" ? "public-read" : "private",
    })

    return {
      url: result.publicUrl,
      key: result.fileId,
    }
  }

  async getUploadStream(
    fileData: FileTypes.ProviderUploadStreamDTO
  ): Promise<UploadStreamResult> {
    // Return writable stream
    const stream = this.client_.createWriteStream(fileData.filename)

    const promise = new Promise((resolve, reject) => {
      stream.on("finish", () => {
        resolve({ url: stream.url, key: stream.fileId })
      })
      stream.on("error", reject)
    })

    return {
      writeStream: stream,
      promise,
      url: stream.url,
      fileKey: stream.fileId,
    }
  }

  async delete(files: FileTypes.ProviderDeleteFileDTO[]): Promise<void> {
    const fileArray = Array.isArray(files) ? files : [files]
    await this.client_.deleteMultiple(
      fileArray.map((f) => f.fileKey)
    )
  }

  async getDownloadStream(
    file: FileTypes.ProviderGetFileDTO
  ): Promise<Readable> {
    return this.client_.createReadStream(file.fileKey)
  }

  async getAsBuffer(
    file: FileTypes.ProviderGetFileDTO
  ): Promise<Buffer> {
    return this.client_.getBuffer(file.fileKey)
  }

  async getPresignedDownloadUrl(
    file: FileTypes.ProviderGetFileDTO
  ): Promise<string> {
    return this.client_.getSignedUrl(file.fileKey, 3600)
  }

  async getPresignedUploadUrl(
    fileData: FileTypes.ProviderGetPresignedUploadUrlDTO
  ): Promise<FileTypes.ProviderFileResultDTO> {
    const signedUrl = await this.client_.getUploadUrl(
      fileData.filename,
      fileData.expiresIn ?? 3600
    )

    return {
      url: signedUrl,
      key: fileData.filename,
    }
  }
}
```

Register your custom provider:

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

export default ModuleProvider(Modules.FILE, {
  services: [CustomFileService],
})
```

## Reference

* Local File Source: `packages/modules/providers/file-local/src/services/local-file.ts`
* S3 Source: `packages/modules/providers/file-s3/src/services/s3-file.ts`
* Base class: `packages/core/utils/src/file/abstract-file-provider.ts`
* Types: `packages/core/types/src/file/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 file upload notifications
  </Card>
</CardGroup>
