Docs · Custom exports (Account)
Built-in presets (YouTube SRT, WebVTT, LMS TTML, and others) cover most freelance deliveries. Pro adds CaptionPass Export Scripts — small JavaScript functions you save on your account and reuse on every project when a client, network, or MAM needs something presets do not ship out of the box.
CPES is not a second caption editor: you still fix timing and text in the project workbench. The script only runs at export time on the saved timeline.
Input is your project's mezzanine timeline — an array of cues with millisecond timings and line text (the same structure as CaptionPass JSON IR). You do not upload a file to the script; the workspace passes the current cues after your edits.
Example input (two cues, abbreviated):
[
{ "index": 1, "startMs": 1200, "endMs": 4200, "lines": ["Welcome back to the channel."] },
{ "index": 2, "startMs": 4200, "endMs": 8100, "lines": ["Today we fix", "overlapping subs."] }
]Output is whatever you return: one file per script run — SRT, VTT, JSON, CSV, or plain text — with a filename you choose. Helpers like ctx.emit.srt(cues) run the same delivery rules engine as built-in presets, optionally with overrides (e.g. shorter max line length).
Prepends a client header, then emits SRT with a tighter line length than the default YouTube preset.
function exportTimeline(cues, ctx) {
var body = ctx.emit.srt(cues, { maxLineLength: 38 });
return {
filename: "partner-delivery.srt",
content: "PARTNER-ID: EXAMPLE\n" + body,
contentType: "text/plain; charset=utf-8"
};
}Example output:
PARTNER-ID: ACME-2026-Q2 PROJECT: Client Reel v3 1 00:00:01,200 --> 00:00:04,200 Welcome back to the channel. 2 00:00:04,200 --> 00:00:08,100 Today we fix overlapping subs.
Transforms cue text, then emits SRT. Useful when delivery notes say "uppercase on all lines" but you still want readable mixed case while editing.
function exportTimeline(cues, ctx) {
var upper = cues.map(function (c) {
return {
index: c.index,
startMs: c.startMs,
endMs: c.endMs,
lines: c.lines.map(function (l) { return l.toUpperCase(); })
};
});
return {
filename: "broadcast-upper.srt",
content: ctx.emit.srt(upper, { maxLineLength: 32 }),
contentType: "text/plain; charset=utf-8"
};
}No subtitle text — a machine-readable sidecar with project title, IR revision, cue count, and span duration. Add this template to a handoff profile next to SRT and VTT files.
function exportTimeline(cues, ctx) {
var durationMs = cues.length
? cues[cues.length - 1].endMs - cues[0].startMs
: 0;
var manifest = {
project: ctx.project.title,
irRevision: ctx.project.irRevision,
cueCount: cues.length,
durationMs: durationMs,
exportedAt: new Date().toISOString()
};
return {
filename: "delivery-manifest.json",
content: JSON.stringify(manifest, null, 2),
contentType: "application/json"
};
}Example output:
{
"project": "Client Reel v3",
"irRevision": 4,
"cueCount": 2,
"durationMs": 6900,
"exportedAt": "2026-05-22T18:00:00.000Z"
}Spreadsheets for client QC or translation vendors: one row per cue with times in milliseconds. You can open this in Excel without parsing SRT.
function exportTimeline(cues, ctx) {
var rows = ["cue_index,start_ms,end_ms,text"];
for (var i = 0; i < cues.length; i++) {
var c = cues[i];
var text = c.lines.join(" / ").replace(/"/g, '""');
rows.push(
c.index + "," + c.startMs + "," + c.endMs + ',"' + text + '"'
);
}
return {
filename: "review-sheet.csv",
content: rows.join("\n"),
contentType: "text/plain; charset=utf-8"
};
}In Account → Custom exports, two buttons exercise your script differently. Do not use Validate when you need to see real project output.
| Control | What it does |
|---|---|
| Validate script | Syntax check plus a QuickJS dry-run on two sample cues. Fast; does not read your project timeline. |
| Test on project | Full run on the selected project IR — shows output filename and preview. Requires a ready project with cues loaded. |
Deeper click-path notes: Dashboard Help → Custom exports.
Define exportTimeline(cues, ctx):
cues — read-only array from your mezzanine timelinectx.emit.srt / vtt / ttml / json — run delivery rules and emittersctx.time.formatSrt / formatVtt — format timestamps if you build text manuallyctx.project — title, irRevisionReturn:
{ filename: "partner.srt", content: "...", contentType: "text/plain; charset=utf-8" }Save templates under Dashboard → Account → Custom exports. Use Single export → Custom on a project, or add templates to a custom handoff profile for zip downloads.
A profile is an ordered list: any built-in preset, any saved CPES template, or both. Example pack for a corporate client: YouTube SRT + WebVTT + delivery-manifest.json (script) in one zip — same workflow as the built-in Freelance handoff profile, but with your filenames and sidecars.
exportTimeline in Library → Custom exports → Validate, then Test on project.API parity: POST /api/v1/export-templates/validate and POST …/{id}/test — see v1 API.
| Symptom | Fix |
|---|---|
exportTimeline is not defined | Declare global function name exactly exportTimeline. |
| Empty zip entry | Return non-empty content and a valid filename. |
| Test mode needs project | Pick a ready project UUID from Library test dropdown or URL path. |
| Script size | 32 KiB max per template |
| Output size | 2 MiB max per file |
| Timeline size | Same cue limits as standard exports |
| Scope | Pro workspace only; scripts cannot call external services or load modules. Dashboard export today — not the public HTTP API. |
Integrators using Bearer keys: see HTTP API (v1) for preset-based processing. CPES is for signed-in Pro editors in the project workspace.