{"record":{"id":"881ff7ce736eea38","repo":"remotion-dev/remotion","slug":"expected-a-remotion-caption-json-array","errorCode":null,"errorMessage":"Expected a Remotion Caption[] JSON array.","messagePattern":"Expected a Remotion Caption\\[\\] JSON array\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/studio/src/components/parse-caption-file.ts","lineNumber":32,"sourceCode":"}: {\n\tfileName: string;\n\tcontents: string;\n}): Caption[] => {\n\tif (!fileName.toLowerCase().endsWith('.json')) {\n\t\tthrow new Error('Unsupported caption file. Choose a .json file.');\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(contents);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('Expected a Remotion Caption[] JSON array.');\n\t}\n\n\tlet previousStart: number | null = null;\n\tfor (const [index, caption] of parsed.entries()) {\n\t\tconst path = `captions[${index}]`;\n\t\tif (!isObject(caption)) {\n\t\t\tthrow new Error(`${path} must be an object.`);\n\t\t}\n\n\t\tif (typeof caption.text !== 'string') {\n\t\t\tthrow new Error(`${path}.text must be a string.`);\n\t\t}\n\n\t\tif (!isFiniteNumber(caption.startMs) || caption.startMs < 0) {\n\t\t\tthrow new Error(`${path}.startMs must be a finite, non-negative number.`);\n\t\t}\n\n\t\tif (!isFiniteNumber(caption.endMs) || caption.endMs < 0) {","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/remotion-dev/remotion/blob/b2f4e34732f3c6c222ea029413e22dc402218cd7/packages/studio/src/components/parse-caption-file.ts#L14-L50","documentation":"parseCaptionFile in Remotion Studio parses a caption .json file and validates it against the Caption[] shape from @remotion/captions. This error is thrown when the parsed JSON is valid JSON but its top-level value is not an array. The parser only accepts an array of caption objects at the root.","triggerScenarios":"Calling parseCaptionFile({fileName: 'captions.json', contents: '...'}) where JSON.parse(contents) yields an object (e.g. {captions: [...]}) or any non-array value (string, number, null).","commonSituations":"Exporting captions wrapped in an object envelope instead of a bare array; hand-editing a caption file and accidentally adding a wrapper; a tool exporting a custom format whose root is an object.","solutions":["Wrap the captions in a bare top-level JSON array: [ {...}, {...} ]","If the file is {\"captions\": [...]}, pass the inner array instead: JSON.parse(contents).captions","Re-export the captions from the source tool choosing the Remotion JSON format"],"exampleFix":"// before\n{\"captions\": [{\"text\": \"hi\", \"startMs\": 0, \"endMs\": 1000}]}\n// after\n[{\"text\": \"hi\", \"startMs\": 0, \"endMs\": 1000}]","handlingStrategy":"validation","validationCode":"const parsed: unknown = JSON.parse(contents);\nif (!Array.isArray(parsed)) {\n  throw new Error('Caption file must be a JSON array at the top level.');\n}","typeGuard":"const isCaptionArray = (v: unknown): v is unknown[] =>\n  Array.isArray(v);","tryCatchPattern":"try {\n  const captions = parseCaptionFile({fileName, contents});\n} catch (e) {\n  console.error('Invalid caption file:', (e as Error).message);\n  // show a user-facing parse error / abort import\n}","preventionTips":["Always export captions as a bare JSON array, not wrapped in an object","Validate the file shape before loading it into Remotion Studio","Keep a sample valid Caption[] file as a reference schema"],"tags":["json","validation","captions"],"backgroundTag":"schema-validation-failed","analyzedSha":"b2f4e34732f3c6c222ea029413e22dc402218cd7","analyzedAt":"2026-09-09T13:27:51.281Z","contentChangedAt":"2026-09-09T13:27:51.281Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}