# S3-compatible storage

> Connect an S3-compatible bucket for source uploads and video outputs.

Connect an S3-compatible bucket to keep source videos and generated video files in your own storage. Chunkify uses the provider's S3 API to read and write files for video assets and transcoding jobs. The connection requires a public HTTPS S3 endpoint, a bucket, and access keys.

## Provider examples

Chunkify recognizes these services by their endpoint and shows their icons in the dashboard:

<ul class="not-prose my-6 grid list-none grid-cols-1 gap-4 p-0 sm:grid-cols-2">
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/backblaze.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>Backblaze B2</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/wasabi.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>Wasabi</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/googlecloud.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>Google Cloud Storage</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/digitalocean.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>DigitalOcean Spaces</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/hetzner.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>Hetzner Object Storage</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/scaleway.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain dark:brightness-0 dark:invert"/>
    <span>Scaleway Object Storage</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/ovh.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>OVHcloud Object Storage</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/akamai.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>Akamai Object Storage</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/vultr.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>Vultr Object Storage</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/idrive.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>IDrive e2</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/storj.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>Storj</span>
  </li>
  <li class="flex min-w-0 items-center gap-3">
    <img src="/assets/images/storage-providers/tigris.svg" alt="" width="32" height="32" class="m-0 size-8 shrink-0 object-contain"/>
    <span>Tigris</span>
  </li>
</ul>

This list is illustrative. You can connect another service, including self-hosted MinIO, if its endpoint meets the requirements below. An unrecognized endpoint appears with a generic storage icon; Chunkify checks access when you create the connection.

## Prepare the bucket and access

Create a bucket with your provider, then collect these values:

- **Endpoint URL:** The public HTTPS origin of its S3 API, without a bucket name in the URL path. It cannot contain credentials, a path, query, or fragment. Chunkify cannot connect to a localhost, private-network, or HTTP-only endpoint.
- **Provider region:** The region identifier used to sign S3 requests. Copy the value required by your provider; it can differ from AWS region names.
- **Chunkify location:** Choose `US`, `EU`, or `ASIA` close to your bucket. This selects where Chunkify processes videos; it does not change the provider region.
- **Access key and secret key:** Credentials with access to the bucket and the object keys Chunkify will use.

Grant the credentials permission to write and read objects under your output prefix, such as `chunkify/`. Chunkify checks a connection by writing a test object there, reading its metadata and contents, then attempting to delete it. For later output cleanup, the credentials also need to list and delete generated objects. If you will upload source videos into this bucket, grant write and read access to their chosen keys as well. Your provider's access-control syntax may differ from AWS IAM.

The S3 API must support signed object uploads and downloads, object metadata, listing, and deletion. Chunkify does not require object tagging on connected storage. The connection check tests a small read and write, so it does not verify every later upload or cleanup operation.

<note>
Probe deletion is best effort: a connection can succeed even if its test object cannot be deleted. On a versioned bucket, allow deletion of object versions if your provider supports it. Otherwise, validation probes or deleted outputs may leave retained versions.
</note>

Use your provider's values. These examples show where some common services expose them:

| Service | Endpoint example | Provider region | Credentials |
| --- | --- | --- | --- |
| [Backblaze B2](https://www.backblaze.com/docs/en/cloud-storage-call-the-s3-compatible-api) | `https://s3.us-west-004.backblazeb2.com` | `us-west-004` | Application key ID and application key |
| [Wasabi](https://docs.wasabi.com/docs/service-urls-for-wasabis-storage-regions) | `https://s3.eu-west-2.wasabisys.com` | `eu-west-2` | Access key and secret key |
| [MinIO](https://min.io/docs/minio/linux/operations/network-encryption.html) | `https://minio.example.com` | Your deployment's configured S3 region | MinIO access key and secret key |
| [Google Cloud Storage](https://docs.cloud.google.com/storage/docs/interoperability) | `https://storage.googleapis.com` | `auto` | Cloud Storage HMAC access ID and secret |

For Google Cloud Storage, use its **XML API** endpoint and HMAC keys. OAuth credentials for Google's JSON API do not fill the S3 access-key fields. Backblaze B2 requires an application key rather than a master key for its S3-compatible API. See the linked provider documentation for the current values for your bucket.

## Connect the bucket with the Chunkify dashboard

1. In your Project, open **Project settings → Storage** and select **New storage**.
2. Choose **S3-compatible**. Enter the **Provider region**, **Chunkify location**, **Endpoint URL**, **Bucket**, **Access key**, and **Secret key**.
3. Set the **Output prefix** to `chunkify/` or another prefix for generated files. If you have a delivery domain, enter its HTTPS origin as the **CDN base URL**. **Allow public access** records whether objects are publicly readable; it does not change the provider's permissions.
4. Select **Create storage**. Chunkify tries virtual-hosted and path-style S3 addresses and saves the style that passes its connection check. If both fail, check the endpoint, signing region, credentials, and bucket permissions.
5. To use this storage for new source uploads and video outputs by default, edit the connection, enable **Use as default storage**, and save.

The output prefix applies to generated files, not source object keys. See [Output paths](/docs/video-streaming/storage/output-paths) for how Chunkify builds object keys.

## Connect with an SDK

Create the connection with a Project access token. This example uses Backblaze B2; replace its endpoint, signing region, bucket, credentials, and Chunkify location with yours. The second call makes this the Project default for new uploads and video outputs. Omit that call if you will select the storage individually; updating the Project requires a Team access token.

<codegroup>
<codeblock lang="Typescript">
```typescript
import Chunkify from '@chunkify/chunkify';

const client = new Chunkify({
  projectAccessToken: 'My Project Access Token',
});

const storage = await client.storages.create({
  storage: {
    provider: 's3_compatible',
    region: 'us-west-004',
    location: 'US',
    endpoint: 'https://s3.us-west-004.backblazeb2.com',
    bucket: 'my-video-bucket',
    access_key_id: 'My B2 Application Key ID',
    secret_access_key: 'My B2 Application Key',
    base_prefix: 'chunkify/',
    public: false,
  },
});

// Optional: use this storage as the Project default.
const teamClient = new Chunkify({
  teamAccessToken: 'My Team Access Token',
});

await teamClient.projects.update('project_id', {
  storage_id: storage.id,
});
```
</codeblock>
<codeblock lang="Python">
```python
from chunkify import Chunkify

client = Chunkify(project_access_token="My Project Access Token")

storage = client.storages.create(
    storage={
        "provider": "s3_compatible",
        "region": "us-west-004",
        "location": "US",
        "endpoint": "https://s3.us-west-004.backblazeb2.com",
        "bucket": "my-video-bucket",
        "access_key_id": "My B2 Application Key ID",
        "secret_access_key": "My B2 Application Key",
        "base_prefix": "chunkify/",
        "public": False,
    },
)

# Optional: use this storage as the Project default.
team_client = Chunkify(team_access_token="My Team Access Token")
team_client.projects.update(
    project_id="project_id",
    storage_id=storage.id,
)
```
</codeblock>
<codeblock lang="PHP">
```php
<?php

use Chunkify\Client;

$client = new Client(projectAccessToken: 'My Project Access Token');

$storage = $client->storages->create(storage: [
    'provider' => 's3_compatible',
    'region' => 'us-west-004',
    'location' => 'US',
    'endpoint' => 'https://s3.us-west-004.backblazeb2.com',
    'bucket' => 'my-video-bucket',
    'accessKeyID' => 'My B2 Application Key ID',
    'secretAccessKey' => 'My B2 Application Key',
    'basePrefix' => 'chunkify/',
    'public' => false,
]);

// Optional: use this storage as the Project default.
$teamClient = new Client(teamAccessToken: 'My Team Access Token');
$teamClient->projects->update(
    'project_id',
    storageID: $storage->id,
);
```
</codeblock>
<codeblock lang="Go">
```go
import (
    "context"

    "github.com/chunkifydev/chunkify-go"
    "github.com/chunkifydev/chunkify-go/option"
)

ctx := context.Background()
client := chunkify.NewClient(
    option.WithProjectAccessToken("My Project Access Token"),
)

storage, err := client.Storages.New(ctx, chunkify.StorageNewParams{
    OfS3Compatible: &chunkify.StorageNewParamsStorageS3Compatible{
        Region: "us-west-004",
        Location: "US",
        Endpoint: "https://s3.us-west-004.backblazeb2.com",
        Bucket: "my-video-bucket",
        AccessKeyID: "My B2 Application Key ID",
        SecretAccessKey: "My B2 Application Key",
        BasePrefix: chunkify.String("chunkify/"),
        Public: chunkify.Bool(false),
    },
})
if err != nil {
    panic(err)
}

// Optional: use this storage as the Project default.
teamClient := chunkify.NewClient(
    option.WithTeamAccessToken("My Team Access Token"),
)
err = teamClient.Projects.Update(ctx, "project_id", chunkify.ProjectUpdateParams{
    StorageID: chunkify.String(storage.ID),
})
if err != nil {
    panic(err)
}
```
</codeblock>
</codegroup>

## Configure browser access and delivery

**Browser uploads go directly to the provider's S3 endpoint** using a signed `PUT` URL. If you use browser uploads, configure the bucket's CORS policy to allow `PUT` and `Content-Type` from the websites that host the uploader.

**Playback goes through your delivery domain**, using `GET` or `HEAD` and sometimes a `Range` header. Set the connection's **CDN base URL** to the HTTPS origin that serves the bucket's object keys. The delivery response must allow the viewing website's origin. Providers use different CORS formats and delivery products; use the [CORS guide](/docs/video-streaming/storage/cors) for the required browser behavior and [CDN delivery](/docs/video-streaming/storage/cdn-delivery) for URL mapping and checks.

## Check the setup

The connection check verifies that Chunkify can write, inspect, and read an object. To check the configured output prefix, create a small video asset and inspect its files in the bucket. If you use browser uploads or CDN playback, test them from your website, including the HLS stream, poster, and timeline previews. A successful connection check does not verify browser access or delivery.

You can later change the Project default or edit the connection's output prefix, CDN base URL, and credentials under **Project settings → Storage**. Changing the prefix does not move existing files. See the [storage overview](/docs/video-streaming/storage) before changing a connection that already holds video files.
