# Output paths

> Choose where video asset and Transcoding outputs go in your storage.

The storage output prefix applies to video asset and Transcoding outputs. A video asset uses a folder template; a transcode uses the output filename you request.

## Storage output prefix

In your storage configuration you can set an **output prefix**, named `base_prefix` in the API. For example, `media/` places generated files below that prefix; an empty prefix writes them at the bucket root. This prefix only applies to output files.

Chunkify stores a nonempty prefix with a trailing slash, so `media` and `media/` select the same folder.

Changing the prefix does not move existing files. A video asset saves its full output folder when it is created, so later outputs stay there. A transcode also saves its output prefix at creation.

## Video asset output folders

### Path template

Set **Asset path template** in **Settings → Streaming** to choose the output folder for future video assets. The API setting is `asset_defaults.path_template` on the Project. Updating `asset_defaults` replaces the whole defaults object, so include the other settings you want to keep.

An individual video asset can override the Project default with `path_template` when it is created. If neither is set, Chunkify uses `{assetID}`. The resolved folder is relative to the selected output storage's `base_prefix`. Source and Upload input locations are independent.

Chunkify temporary storage always uses `{assetID}/` and does not use a custom template or require template metadata.

### Template variables and rules

Templates support literal text, `{assetID}`, and `{metadata.<key>}`. Metadata is a flat map of strings with case-sensitive keys. There are no nested-object lookups or expressions.

With `metadata.VideoID` set to `video-123` and a video asset ID of `ast_2G6MJiNz71bHQGNzGwKx5cJwPFS`:

| Template | Relative output folder |
| --- | --- |
| `{assetID}` | `ast_2G6MJiNz71bHQGNzGwKx5cJwPFS/` |
| `videos/{metadata.VideoID}` | `videos/video-123/` |
| `videos/{metadata.VideoID}/{assetID}` | `videos/video-123/ast_2G6MJiNz71bHQGNzGwKx5cJwPFS/` |

Unknown variables and malformed placeholders are rejected. Referenced metadata must exist and be non-empty. Slashes in a metadata value create subfolders, so `series/episode-1` remains `series/episode-1`. Values are substituted once; text such as `{assetID}` inside a metadata value stays literal.

Expanded video asset folders cannot have leading or repeated slashes, `.` or `..` segments, backslashes, URL schemes, query strings, fragments, or control characters. Chunkify does not rewrite identifiers. A single trailing slash is accepted.

Templates can contain up to 1,024 UTF-8 bytes. The complete expanded prefix, including the storage output prefix and trailing slash, can contain up to 768 bytes. The remaining 256 bytes are reserved for generated directories and filenames within the 1,024-byte object-key limit.

### Create a video asset with a custom folder

Run these examples on your server with customer storage set as the Project default. Chunkify temporary storage ignores custom templates. Replace `src_2G6MJiNz71bHQGNzGwKx5cJwPFS` with an existing Source ID.

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

const client = new Chunkify({ projectAccessToken: process.env.CHUNKIFY_PROJECT_TOKEN });
const asset = await client.assets.create({
    source_id: 'src_2G6MJiNz71bHQGNzGwKx5cJwPFS',
    path_template: 'videos/{metadata.VideoID}',
    metadata: { VideoID: 'video-123' },
});
```
</codeblock>
<codeblock lang="Python">
```python
import os
from chunkify import Chunkify

client = Chunkify(project_access_token=os.environ["CHUNKIFY_PROJECT_TOKEN"])
asset = client.assets.create(
    source_id="src_2G6MJiNz71bHQGNzGwKx5cJwPFS",
    path_template="videos/{metadata.VideoID}",
    metadata={"VideoID": "video-123"},
)
```
</codeblock>
<codeblock lang="PHP">
```php
use Chunkify\Client;

$client = new Client(projectAccessToken: getenv('CHUNKIFY_PROJECT_TOKEN'));
$asset = $client->assets->create(
    sourceID: 'src_2G6MJiNz71bHQGNzGwKx5cJwPFS',
    pathTemplate: 'videos/{metadata.VideoID}',
    metadata: ['VideoID' => 'video-123'],
);
```
</codeblock>
<codeblock lang="Go">
```go
import (
	"context"
	"fmt"
	"os"

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

client := chunkify.NewClient(
	option.WithProjectAccessToken(os.Getenv("CHUNKIFY_PROJECT_TOKEN")),
)
asset, err := client.Assets.New(context.Background(), chunkify.AssetNewParams{
	SourceID:     "src_2G6MJiNz71bHQGNzGwKx5cJwPFS",
	PathTemplate: chunkify.String("videos/{metadata.VideoID}"),
	Metadata:     map[string]string{"VideoID": "video-123"},
})
if err != nil {
	panic(err)
}

fmt.Println(asset.ID)
```
</codeblock>
</codegroup>

If the selected storage has an output prefix of `media/`, this call saves the HLS master playlist at:

```text
media/videos/video-123/manifest.m3u8
```

Chunkify resolves the template once and saves the destination with the video asset. HLS renditions and manifests, posters, timeline previews, and static renditions use that folder. Static renditions added later use the same saved destination. Changing Project settings or video asset metadata does not move existing files or change their object keys.

### Generated object paths

For a template that also includes the video asset ID, suppose the storage prefix is `media/`, the template is `videos/{metadata.VideoID}/{assetID}`, `VideoID` is `video-123`, and the video asset ID is `ast_2G6MJiNz71bHQGNzGwKx5cJwPFS`. Its output folder is:

```text
media/videos/video-123/ast_2G6MJiNz71bHQGNzGwKx5cJwPFS/
```

Chunkify writes the HLS master playlist at `media/videos/video-123/ast_2G6MJiNz71bHQGNzGwKx5cJwPFS/manifest.m3u8`, the poster at `media/videos/video-123/ast_2G6MJiNz71bHQGNzGwKx5cJwPFS/poster.jpg`, and the timeline preview index at `media/videos/video-123/ast_2G6MJiNz71bHQGNzGwKx5cJwPFS/thumbnails/images.vtt`. HLS renditions, thumbnail images, and later static renditions or captions also belong to this folder.

These are object keys inside the bucket, not playback URLs. The video asset response exposes core playback, poster, and default timeline paths. Read [Asset outputs](/docs/video-streaming/outputs) for the paths of static renditions, captions, and additional thumbnails. Your application does not need to guess their filenames.

### Folder ownership and conflicts

Chunkify reserves each video asset output folder against other video assets in the same storage. Creation returns HTTP 409 if another video asset owns the same folder, a parent folder, or a child folder. For example, `videos/123/` conflicts with `videos/123/hd/`, but not with `videos/1234/`. Sibling folders succeed, and equal paths in different storage connections are independent. Concurrent requests cannot claim overlapping folders.

The check uses existing video asset records only. It does not check standalone Jobs or objects already in the bucket. A Job can write into a video asset's folder, and matching keys can be overwritten. Deleting a video asset with `delete_files=true` removes objects under its saved prefix, including any unrelated files there. Choose separate folders for standalone Jobs.

### Video assets created from uploads

With `create_asset: true`, create the Upload session on your server and include every metadata key referenced by the Project template. For `videos/{metadata.VideoID}`, provide `metadata: { VideoID: 'video-123' }`. Missing or empty values are rejected before upload credentials are returned. With `create_asset: false`, these video asset metadata requirements do not apply.

The Upload saves the Project template and output settings when the session is created. Later Project changes do not affect it. Pass the returned Upload session to [Chunkify Uploader](/docs/integration/uploader/setup), which handles transfer and completion.

No folder is reserved during transfer. At completion, Chunkify creates the video asset and checks folder ownership atomically. A conflict returns HTTP 409, marks the Upload `failed` with the reason, and emits `upload.failed`. Source creation, video asset creation, and usage changes from that completion are rolled back. Further completion calls follow the terminal failure behavior and return a validation error. Correct the metadata or Project template and create a new Upload session.

## Transcode output paths

A transcode writes its output to the Project default storage unless you override `storage.id`. The input Source can use another storage configuration.

For your storage, supply `storage.path` as a filename relative to `base_prefix`. The extension must match the resolved output: `.mp4`, `.webm`, or `.m4a`.

With `base_prefix: media/` and `storage.path: exports/video.mp4`, the final key is `media/exports/video.mp4`. Do not include the prefix twice. Video asset folder templates do not apply to Transcodings.

These examples use an existing Source and the Project default output storage:

<ignore>
SDK publication check: drafted from api/openapi.json and existing SDK conventions. Add Transcodings models, create/retrieve/list/cancel/delete mappings, pagination and data unwrapping to the owning SDK generator configuration. Verify generated method names and Go union/format/storage types in TypeScript, Python, PHP and Go before publication.
</ignore>

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

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

    const transcoding = await client.transcodings.create({
        source_id: 'src_2G6MJiNz71bHQGNzGwKx5cJwPFS',
        format: {
            id: "mp4_h264",
        },
        storage: {
            path: 'exports/video.mp4',
        },
    });

    console.log(transcoding.id, transcoding.status);
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
```
</codeblock>
<codeblock lang="Python">
```python
from chunkify import Chunkify

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

transcoding = client.transcodings.create(
    source_id="src_2G6MJiNz71bHQGNzGwKx5cJwPFS",
    format={
        "id": "mp4_h264",
    },
    storage={
        "path": "exports/video.mp4",
    },
)

print(transcoding.id, transcoding.status)
```
</codeblock>
<codeblock lang="PHP">
```php
<?php

use Chunkify\Client;

require_once __DIR__ . '/vendor/autoload.php';

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

$transcoding = $client->transcodings->create(
    sourceID: 'src_2G6MJiNz71bHQGNzGwKx5cJwPFS',
    format: [
        'id' => 'mp4_h264',
    ],
    storage: [
        'path' => 'exports/video.mp4',
    ],
);

var_dump($transcoding->id, $transcoding->status);
```
</codeblock>
<codeblock lang="Go">
```go
package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	client := chunkify.NewClient(
		option.WithProjectAccessToken("My Project Access Token"),
	)

	transcoding, err := client.Transcodings.New(
		context.Background(),
		chunkify.TranscodingNewParams{
			SourceID: "src_2G6MJiNz71bHQGNzGwKx5cJwPFS",
			Format: chunkify.TranscodingNewParamsFormatUnion{
				OfMP4H264: &chunkify.TranscodingMP4H264Param{},
			},
			Storage: chunkify.TranscodingNewParamsStorage{
				Path: chunkify.String("exports/video.mp4"),
			},
		},
	)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(transcoding.ID, transcoding.Status)
}
```
</codeblock>
</codegroup>


The transcode saves its prefix when created, so changing the storage prefix later does not move its files. Use a distinct filename for each export.

Chunkify temporary storage generates the path automatically and deletes outputs 24 hours after publication. See [Lifecycle and files](/docs/transcoding/lifecycle#retention-and-deletion).

<a id="transcoding-job-output-paths"></a>

## Legacy Job output paths

Legacy Jobs use the storage prefix at upload time, so later prefix changes can affect their pending output.

For a transcoding Job using your storage, you are required to set `storage.path` to the output filename and its folder relative to that storage configuration prefix.

<note> Video asset path templates do not affect transcode jobs. </note>

Suppose your connected storage has these settings (other fields omitted):

```json
{
  "id": "stor_aws_2G6MJiNz71bHQGNzGwKx5cJwPFS",
  "base_prefix": "media/"
}
```

Create the Job with a path relative to that prefix:

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

const client = new Chunkify({ projectAccessToken: 'My Project Access Token' });
const job = await client.jobs.create({
  source_id: 'src_2G6MJiNz71bHQGNzGwKx5cJwPFS',
  format: { id: 'mp4_h264' },
  storage: {
    id: 'stor_aws_2G6MJiNz71bHQGNzGwKx5cJwPFS',
    path: 'exports/video.mp4',
  },
});
```
</codeblock>
<codeblock lang="Python">
```python
from chunkify import Chunkify

client = Chunkify(project_access_token="My Project Access Token")
job = client.jobs.create(
    source_id="src_2G6MJiNz71bHQGNzGwKx5cJwPFS",
    format={"id": "mp4_h264"},
    storage={
        "id": "stor_aws_2G6MJiNz71bHQGNzGwKx5cJwPFS",
        "path": "exports/video.mp4",
    },
)
```
</codeblock>
<codeblock lang="PHP">
```php
use Chunkify\Client;

$client = new Client(projectAccessToken: 'My Project Access Token');
$job = $client->jobs->create(
    sourceID: 'src_2G6MJiNz71bHQGNzGwKx5cJwPFS',
    format: ['id' => 'mp4_h264'],
    storage: [
        'id' => 'stor_aws_2G6MJiNz71bHQGNzGwKx5cJwPFS',
        'path' => 'exports/video.mp4',
    ],
);
```
</codeblock>
<codeblock lang="Go">
```go
import (
    "context"

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

client := chunkify.NewClient(
    option.WithProjectAccessToken("My Project Access Token"),
)
_, err := client.Jobs.New(context.Background(), chunkify.JobNewParams{
    SourceID: "src_2G6MJiNz71bHQGNzGwKx5cJwPFS",
    Format: chunkify.JobNewParamsFormatUnion{
        OfMP4H264: &chunkify.MP4H264Param{},
    },
    Storage: chunkify.JobNewParamsStorage{
        ID:   chunkify.String("stor_aws_2G6MJiNz71bHQGNzGwKx5cJwPFS"),
        Path: chunkify.String("exports/video.mp4"),
    },
})
if err != nil {
    panic(err)
}
```
</codeblock>
</codegroup>

Chunkify writes the output at `media/exports/video.mp4`. Do not include `media/` in `storage.path`; Chunkify adds it automatically.

Use a filename that matches the format, such as `.mp4`, `.webm`, `.m3u8`, or `.vtt`. A JPG image sequence needs `%05d` in its `.jpg` filename. Chunkify removes leading slashes for compatibility, but write paths without them.

HLS caption Jobs in a manifest group must use the same output folder as the group's other Jobs. Keep unrelated Jobs in separate folders to avoid replacing existing files. When using Chunkify temporary storage, Chunkify chooses the Job output path.
