import S3BucketAcl from "../../../troubleshooting/_sections/s3/aws-bucket-acl.mdx"
import CloudflareChecksum from "../../../troubleshooting/_sections/s3/cloudflare-checksum.mdx"

import {
  Table,
  Tabs,
  TabsList,
  TabsContent,
  TabsContentWrapper,
  TabsTrigger,
  DetailsList,
} from "docs-ui"

export const metadata = {
  title: `S3 File Module Provider`,
}

# {metadata.title}

The S3 File Module Provider integrates Amazon S3 and services following a compatible API (such as MinIO or DigitalOcean Spaces) to store files uploaded to your Medusa application.

<Note title="Looking for managed storage?">

Cloud offers a managed file storage solution with AWS S3 for your Medusa application. Refer to the [S3](!cloud!/s3) Cloud documentation for more details.

</Note>

## Prerequisites

<Tabs defaultValue="aws">
  <TabsList>
    <TabsTrigger value="aws">AWS S3</TabsTrigger>
    <TabsTrigger value="minio">MinIO</TabsTrigger>
    <TabsTrigger value="spaces">DigitalOcean Spaces</TabsTrigger>
    <TabsTrigger value="supabase">Supabase S3 Storage</TabsTrigger>
    <TabsTrigger value="cloudflare">Cloudflare R2</TabsTrigger>
  </TabsList>
  <TabsContentWrapper>
    <TabsContent value="aws">

    - [AWS account](https://console.aws.amazon.com/console/home?nc2=h_ct&src=header-signin).
    - Create [AWS user with AmazonS3FullAccess permissions](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-and-attach-iam-policy.html).
    - Create [AWS user access key ID and secret access key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey).
    - Create [S3 bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/create-bucket-overview.html) with the "Public Access setting" enabled:
      1. On your bucket's dashboard, click on the Permissions tab.
      2. Click on the Edit button of the Block public access (bucket settings) section.
      3. In the form that opens, don't toggle any checkboxes and click the "Save changes" button.
      4. Confirm saving the changes by entering `confirm` in the pop-up that shows.
      5. Back on the Permissions page, scroll to the Object Ownership section and click the Edit button.
      6. In the form that opens:
          - Choose the "ACLs enabled" card.
          - Click on the "Save changes" button.
      7. Back on the Permissions page, scroll to the "Access Control List (ACL)" section and click on the Edit button.
      8. In the form that opens, enable the Read permission for "Everyone (public access)".
      9. Check the "I understand the effects of these changes on my objects and buckets." checkbox.
      10. Click on the "Save changes" button.

    </TabsContent>
    <TabsContent value="minio">

    - [Install MinIO](https://min.io/docs/minio/linux/index.html).
    - Change port to `9001` using the [console address](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#minio.server.-console-address) and [address](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#minio.server.-address) CLI options.
    - Create MinIO access and secret access key:
        - Go to User -> Access Keys
        - Click on the Create Access Keys button.
        - Click on the Create button.
        - Copy the keys of the pop-up. Make sure to copy the secret key as it won't be shown again.
    - Create a [MinIO bucket](https://min.io/docs/minio/linux/administration/console/managing-objects.html#creating-buckets) with public access policy. To add the policy:
        - Go to the bucket's dashboard from Administrator -> Buckets.
        - Under the Summary section, click on the pencil icon next to Access Policy.
        - In the pop-up, choose Custom from the dropdown.
        - In the editor, enter the following:

    ```json highlights={[["15", "{bucketname}", "Replace with the bucket's name."]]}
    {
      "Version": "2012-10-17",
      "Statement": [
        {
        "Action": [
          "s3:GetObject"
        ],
        "Effect": "Allow",
        "Principal": {
          "AWS": [
          "*"
          ]
        },
        "Resource": [
          "arn:aws:s3:::{bucketname}/*"
        ]
        }
      ]
    }
    ```

    Make sure to replace `{bucket_name}` with the name of the bucket you created. You can edit the access policy under the Summary section of your bucket's dashboard.

    </TabsContent>
    <TabsContent value="spaces">

    - Create [DigitalOcean account](https://cloud.digitalocean.com/registrations/new).
    - Create [DigitalOcean Spaces bucket](https://docs.digitalocean.com/products/spaces/how-to/create/).
    - Create [DigitalOcean Spaces access and secret access keys](https://docs.digitalocean.com/products/spaces/how-to/manage-access/#access-keys).

    </TabsContent>
    <TabsContent value="supabase">

    - [Supabase account](https://supabase.com/dashboard/sign-in) with a project.
    - Create [New Public S3 bucket](https://supabase.com/docs/guides/storage/buckets/creating-buckets?queryGroups=language&language=dashboard).
    - Create [New S3 Access Keys](https://supabase.com/docs/guides/storage/s3/authentication?queryGroups=language&language=javascript#s3-access-keys).
    - Create [Storage Policy](https://supabase.com/docs/guides/storage/security/access-control).
      1. On your bucket's dashboard, click on Policies under the Configuration sidebar.
      2. Click on New Policy under "Other policies under storage.objects".
      3. Click get started quickly, choose "insert access for authenticated users only", and click the "Use this template" button.
      4. Click ALL for "Allowed Operations", click the "Review" button, then the "Save policy" button.

    </TabsContent>
    <TabsContent value="cloudflare">
    1. Create a [Cloudflare account](https://dash.cloudflare.com/sign-up).
    2. Set up your R2 bucket:
       - Navigate to R2 Object Storage in your dashboard. You may need to provide your credit-card information.
       - Click "Create bucket"
       - Enter a unique bucket name
       - Select "Automatic" for location
       - Choose "Standard" for storage class
       - Confirm by clicking "Create bucket"
    3. Configure public access:
       - Make sure you have a [domain configured in your Cloudflare account](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/).
       - On your bucket's dashboard, click on the Settings tab.
       - In the General Section look for Custom Domains (recommended for production use)
       - Click on the Add button to add your domain name.
       - Enter the domain name you want to connect to and select Continue.
       - Review the new record that will be added to the DNS table and select Connect Domain.
    4. Retrieve credentials:
       - [Go to API tokens page](https://dash.cloudflare.com/?to=/:account/r2/api-tokens):
         - Click "Create User API token"
         - Edit the "R2 Token" name
         - Under Permissions, select Object Read & Write permission types
         - You can optionally specify the buckets that this API token has access to under the "Specify bucket(s)" section.
         - Once done, click the "Create User API Token" button.
         - Copy the jurisdiction-specific endpoint for S3 clients to S3_ENDPOINT into your environment variables.
         - Copy the Access Key ID and Secret Access Key to the corresponding fields into your environment variables.
         - Copy your custom domain to `S3_FILE_URL` with leading https:// into your environment variables.

    </TabsContent>

  </TabsContentWrapper>
</Tabs>

---

## Register the S3 File Module

Add the module into the `providers` array of the File Module:

<Note>

The File Module accepts one provider only.

</Note>

```ts title="medusa-config.ts"
import { Modules } from "@medusajs/framework/utils"

// ...

module.exports = {
  // ...
  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,
              // other options...
            },
          },
        ],
      },
    },
  ],
}
```

### Additional Configuration for MinIO and Supabase

If you're using MinIO or Supabase, set `forcePathStyle` to `true` in the `additional_client_config` object.

For example:

```ts title="medusa-config.ts"
module.exports = defineConfig({
  // ...
  modules: [
    {
      resolve: "@medusajs/medusa/file",
      options: {
        providers: [
          {
            resolve: "@medusajs/medusa/file-s3",
            id: "s3",
            options: {
              // ...
              additional_client_config: {
                forcePathStyle: true,
              },
            },
          },
        ],
      },
    },
  ],
})
```

### S3 File Module Options

<Table>
  <Table.Header>
    <Table.Row>
      <Table.HeaderCell>Option</Table.HeaderCell>
      <Table.HeaderCell>Description</Table.HeaderCell>
      <Table.HeaderCell>Default</Table.HeaderCell>
    </Table.Row>
  </Table.Header>
  <Table.Body>
    <Table.Row>
      <Table.Cell>
      
      `file_url`
      
      </Table.Cell>
      <Table.Cell>
      
      The base URL to upload files to.

      - For AWS S3, the endpoint is of the format `https://{bucket}.s3.{region}.amazonaws.com`
      - For MinIO, it's the URL to the MinIO server with the bucket's name. For example, `https://{server-domain}/{bucket}`. Locally, it may be something like `http://192.168.0.123:9001/{bucket}`.
      - For DigitalOcean Spaces, it's either the Origin Endpoint or the CDN endpoint of your Spaces Object Storage bucket.
      - for Supabase, it's `https://{uniqueID}.supabase.co/storage/v1/object/public/{bucket}`. You can retrieve the `uniqueID` from [Storage Settings](https://supabase.com/docs/guides/storage/s3/authentication?queryGroups=language&language=javascript#s3-access-keys) page in the Endpoint URL.
      - For Cloudflare R2, it's your `Custom Domain` (recommended for production use) or `Public R2.dev Bucket URL` (for development).

      </Table.Cell>
      <Table.Cell>

      \-

      </Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>

      `access_key_id`

      </Table.Cell>
      <Table.Cell>

      The AWS or (S3 compatible) user's access key ID.

      </Table.Cell>
      <Table.Cell>

      \-

      </Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>

      `secret_access_key`

      </Table.Cell>
      <Table.Cell>

      The AWS or (S3 compatible) user's secret access key.

      </Table.Cell>
      <Table.Cell>

      \-

      </Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>

      `region`

      </Table.Cell>
      <Table.Cell>

      The bucket's region code.

      For MinIO, use `us-east-1`.

      For Cloudflare, use `auto`.

      </Table.Cell>
      <Table.Cell>

      \-

      </Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>

      `bucket`

      </Table.Cell>
      <Table.Cell>

      The bucket's name.

      </Table.Cell>
      <Table.Cell>

      \-

      </Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>

      `endpoint`

      </Table.Cell>
      <Table.Cell>

      The URL to the AWS S3 (or compatible S3 API) server.

      - For AWS S3, the endpoint is of the format `https://s3.{region}.amazonaws.com`
      - For MinIO, it's the URL to the MinIO server. For example, locally, it may be something like `http://192.168.0.123:9001`.
      - For DigitalOcean Spaces, it's the Spaces Origin Endpoint of the format `https://{region}.digitaloceanspaces.com`.
      - For Supabase, it's the Endpoint URL in the [Storage Settings](https://supabase.com/docs/guides/storage/s3/authentication?queryGroups=language&language=javascript#s3-access-keys).
      - For Cloudflare, it's the endpoint for S3 clients `https://{your-account-id}.r2.cloudflarestorage.com`.

      </Table.Cell>
      <Table.Cell>

      \-

      </Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>

      `prefix`

      </Table.Cell>
      <Table.Cell>

      A string to prefix each uploaded file's name.

      </Table.Cell>
      <Table.Cell>

      \-

      </Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>

      `cache_control`

      </Table.Cell>
      <Table.Cell>

      A string indicating how long objects remain in the AWS S3 (or compatible S3 API) cache.

      </Table.Cell>
      <Table.Cell>

      `public, max-age=31536000`

      </Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>

      `download_file_duration`

      </Table.Cell>
      <Table.Cell>

      A number indicating the expiry time of presigned URLs in seconds.

      </Table.Cell>
      <Table.Cell>

      `3600` (An hour)

      </Table.Cell>
    </Table.Row>
    <Table.Row>
      <Table.Cell>

      `additional_client_config`

      </Table.Cell>
      <Table.Cell>

      Any additional configurations to pass to the S3 client.

      Refer to [this AWS API reference](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html) for a full list of accepted configuration.

      </Table.Cell>
      <Table.Cell>

      \-

      </Table.Cell>
    </Table.Row>

  </Table.Body>
</Table>

---

## Troubleshooting

<DetailsList
  sections={[
    {
      title:
        "AWS: The bucket does not allow ACLs (Enabling public access to bucket)",
      content: <S3BucketAcl />,
    },
    {
      title: "Cloudflare: Checksum error",
      content: <CloudflareChecksum />,
    },
  ]}
/>
