remotion-dev/remotion · error · TypeError
"${name}" must be a [number, number] tuple
Error message
"${name}" must be a [number, number] tuple What it means
Thrown by assertOptionalUvCoordinate when the fisheye() effect receives a 'center' value that is defined but is not an array of exactly two finite numbers. The fisheye effect expects 'center' as a UV-space [x, y] coordinate (each 0..1) so the lens distortion can be offset within the frame. Any other shape (string, object, length-3 array, NaN/Infinity member, nested arrays) is rejected because the GLSL sampler would otherwise read garbage.
Source
Thrown at packages/effects/src/fisheye/index.ts:110
const resolve = (p: FisheyeParams): FisheyeResolved => ({
fieldOfView: p.fieldOfView ?? DEFAULT_FISHEYE_FIELD_OF_VIEW,
center: [...(p.center ?? DEFAULT_FISHEYE_CENTER)] as FisheyeUvCoordinate,
radius: p.radius ?? DEFAULT_FISHEYE_RADIUS,
zoom: p.zoom ?? DEFAULT_FISHEYE_ZOOM,
});
const assertOptionalUvCoordinate = (value: unknown, name: string): void => {
if (value === undefined) {
return;
}
if (
!Array.isArray(value) ||
value.length !== 2 ||
value.some((item) => typeof item !== 'number' || !Number.isFinite(item))
) {
throw new TypeError(`"${name}" must be a [number, number] tuple`);
}
};
const validatePositive = (value: number, name: string): void => {
if (value <= 0) {
throw new TypeError(
`"${name}" must be greater than 0, but got ${JSON.stringify(value)}`,
);
}
};
const validateFisheyeParams = (params: FisheyeParams): void => {
assertEffectParamsObject(params, 'Fisheye');
assertOptionalFiniteNumber(params.fieldOfView, 'fieldOfView');
assertOptionalUvCoordinate(params.center, 'center');
assertOptionalFiniteNumber(params.radius, 'radius');
assertOptionalFiniteNumber(params.zoom, 'zoom');
View on GitHub (pinned to 78fe4bb3fd)
Solutions
- Pass center as an explicit two-number tuple, e.g. fisheye({center: [0.5, 0.5]}).
- If the value comes from JSON or user input, coerce and validate with Number() and isFinite before calling fisheye().
- Leave center undefined to use the default [0.5, 0.5].
Example fix
// before
fisheye({center: 0.5});
fisheye({center: ['0.5', '0.5']});
// after
fisheye({center: [0.5, 0.5]});
fisheye({center: [Number(raw.x), Number(raw.y)]}); Defensive patterns
Strategy: type-guard
Validate before calling
const isUvTuple = (v: unknown): v is [number, number] =>
Array.isArray(v) &&
v.length === 2 &&
v.every((x) => typeof x === 'number' && Number.isFinite(x));
if (center !== undefined && !isUvTuple(center)) {
throw new Error('center must be a [number, number] tuple');
} Type guard
const isUvTuple = (v: unknown): v is [number, number] => Array.isArray(v) && v.length === 2 && v.every((x) => typeof x === 'number' && Number.isFinite(x));
Prevention
- Type the prop as [number, number] in your own component wrappers so TypeScript rejects scalars and wrong arities at compile time.
- Validate deserialized config (JSON, URL params) with the type guard before forwarding to fisheye().
- Keep center undefined unless you genuinely need to offset the lens.
When it happens
Trigger: Calling fisheye({center: 0.5}) with a scalar, fisheye({center: [0.5]}) or fisheye({center: [0.5, 0.5, 0.5]}) with the wrong arity, fisheye({center: ['0.5', '0.5']}) with strings, fisheye({center: [0.5, NaN]}), or passing an object like {x,y} instead of a tuple.
Common situations: Porting center coordinates from libraries that accept {x, y} objects (Framer Motion, GSAP) or [x, y, z] triples; deserializing center from JSON where numbers became strings; off-by-one copy of a vec3 from a 3D scene.
Related errors
- "${name}" must be greater than 0, but got ${JSON.stringify(v
- "fieldOfView" must be <= ${MAX_FIELD_OF_VIEW}, but got ${JSO
- "${name}" must be a [number, number] tuple
- The "width" and "height" props must be numbers on <Img> when
- The ${formatPropList(conflictingProps)} prop${conflictingPro
AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12).
Data as JSON: /api/errors/300950e03c78f52d.
Report an issue: GitHub.