{"record":{"id":"3cc0369019cb84d9","repo":"remotion-dev/remotion","slug":"extrapolateleft-identity-is-not-supported-for-n","errorCode":null,"errorMessage":"extrapolateLeft: \"identity\" is not supported for non-numeric strings","messagePattern":"extrapolateLeft: \"identity\" is not supported for non-numeric strings","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/core/src/interpolate.ts","lineNumber":914,"sourceCode":"\t\t\t}) !== Easing.step1\n\t\t) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'Non-numeric strings can only be interpolated using Easing.step1',\n\t\t\t);\n\t\t}\n\t}\n\n\tconst posterizedInput =\n\t\toptions?.posterize === undefined\n\t\t\t? input\n\t\t\t: Math.floor(input / options.posterize) * options.posterize;\n\tconst inputMin = inputRange[0];\n\tconst inputMax = inputRange[inputRange.length - 1];\n\tlet resolvedInput = posterizedInput;\n\n\tif (resolvedInput < inputMin) {\n\t\tif (options?.extrapolateLeft === 'identity') {\n\t\t\tthrow new TypeError(\n\t\t\t\t'extrapolateLeft: \"identity\" is not supported for non-numeric strings',\n\t\t\t);\n\t\t}\n\n\t\tif (options?.extrapolateLeft === 'wrap') {\n\t\t\tconst wrapRange = inputMax - inputMin;\n\t\t\tresolvedInput =\n\t\t\t\t((((resolvedInput - inputMin) % wrapRange) + wrapRange) % wrapRange) +\n\t\t\t\tinputMin;\n\t\t} else {\n\t\t\treturn outputRange[0];\n\t\t}\n\t}\n\n\tif (resolvedInput > inputMax) {\n\t\tif (options?.extrapolateRight === 'identity') {\n\t\t\tthrow new TypeError(\n\t\t\t\t'extrapolateRight: \"identity\" is not supported for non-numeric strings',","sourceCodeStart":896,"sourceCodeEnd":932,"githubUrl":"https://github.com/remotion-dev/remotion/blob/78fe4bb3fdb5a2cd68724393d63cb223db333fa7/packages/core/src/interpolate.ts#L896-L932","documentation":"Thrown by interpolateDiscreteString when input is below inputRange[0] and options.extrapolateLeft is 'identity'. Identity extrapolation would return the raw (sub-min) input as a number, but a discrete non-numeric string outputRange has no numeric value to return, so the combination is rejected.","triggerScenarios":"interpolate(t, [0, 1], [\"red\", \"blue\"], {easing: Easing.step1, extrapolateLeft: 'identity'}) called with t < 0.","commonSituations":"Reusing options from a numeric interpolate() call (where 'identity' is valid) on a discrete string call; animating a property whose input can undershoot the range.","solutions":["Use extrapolateLeft: 'clamp' (default discrete behavior returns the first output) or 'extend' for non-numeric strings.","Guard the input so it never falls below inputRange[0].","Switch to extrapolateLeft: 'wrap' to cycle through outputs."],"exampleFix":"// before\ninterpolate(t, [0, 1], [\"red\", \"blue\"], {\n  easing: Easing.step1,\n  extrapolateLeft: 'identity',\n});\n// after\ninterpolate(t, [0, 1], [\"red\", \"blue\"], {\n  easing: Easing.step1,\n  extrapolateLeft: 'clamp',\n});","handlingStrategy":"validation","validationCode":"function isDiscreteStringRange(outputRange: readonly (string | number)[]): boolean {\n  return outputRange.some((o) => typeof o === 'string'\n    && o.trim().split(/\\s+/).some((p) => !/^([+-]?(?:\\d+\\.?\\d*|\\.\\d+))([a-zA-Z%]+)?$/.test(p)\n      && !/^(left|right|top|bottom|center)$/i.test(p)));\n}\n\nif (isDiscreteStringRange(outputRange) && options?.extrapolateLeft === 'identity') {\n  throw new Error('Use extrapolateLeft: clamp|extend|wrap for discrete strings');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["For non-numeric string ranges use extrapolateLeft: 'clamp', 'extend', or 'wrap' — never 'identity'.","Clamp the input upstream so it cannot fall below inputRange[0]."],"tags":["interpolate","discrete-string","extrapolation","validation"],"backgroundTag":null,"analyzedSha":"78fe4bb3fdb5a2cd68724393d63cb223db333fa7","analyzedAt":"2026-08-12T17:18:50.444Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}