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 the corner-pin effect's assertOptionalUvCoordinate guard when one of the corner props (topLeft, topRight, bottomRight, bottomLeft) is supplied but is not a two-element array of finite numbers. The corner-pin effect maps the four output corners via UV coordinates, so each corner must be a strict [number, number] tuple. Passing objects, shorter/longer arrays, NaN/Infinity, or non-number elements trips this guard before any WebGL work happens.
Source
Thrown at packages/effects/src/corner-pin/index.ts:88
bottomRight: [
...(p.bottomRight ?? DEFAULT_BOTTOM_RIGHT),
] as CornerPinUvCoordinate,
bottomLeft: [
...(p.bottomLeft ?? DEFAULT_BOTTOM_LEFT),
] as CornerPinUvCoordinate,
});
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 validateCornerPinParams = (params: CornerPinParams): void => {
assertEffectParamsObject(params, 'Corner pin');
assertOptionalUvCoordinate(params.topLeft, 'topLeft');
assertOptionalUvCoordinate(params.topRight, 'topRight');
assertOptionalUvCoordinate(params.bottomRight, 'bottomRight');
assertOptionalUvCoordinate(params.bottomLeft, 'bottomLeft');
};
export const cornerPin = createEffect<CornerPinParams, CornerPinState>({
type: 'dev.remotion.effects.cornerPin',
label: 'cornerPin()',
documentationLink: 'https://www.remotion.dev/docs/effects/corner-pin',
backend: 'webgl2',
calculateKey: (params) => {
const r = resolve(params);View on GitHub (pinned to 78fe4bb3fd)
Solutions
- Pass each corner as a literal two-element numeric array, e.g. topLeft: [0.25, 0.1].
- If coords arrive as objects, map them: topLeft: [pt.x, pt.y] before passing to cornerPin().
- Guard against NaN/Infinity upstream: filter coordinates through Number.isFinite before constructing the params object.
- Omit the corner entirely to accept the documented default rather than passing null or undefined-like sentinels.
Example fix
// before
cornerPin({topLeft: {x: 0, y: 0}, bottomRight: [1, 1]});
// after
const toUv = (p) => [p.x, p.y];
cornerPin({topLeft: toUv({x: 0, y: 0}), bottomRight: [1, 1]}); Defensive patterns
Strategy: type-guard
Validate before calling
const isUv = (v) =>
Array.isArray(v) &&
v.length === 2 &&
v.every((n) => typeof n === 'number' && Number.isFinite(n));
const safeParams = (p) => {
for (const k of ['topLeft', 'topRight', 'bottomRight', 'bottomLeft']) {
if (p[k] !== undefined && !isUv(p[k])) {
throw new Error(`${k} must be [number, number]`);
}
}
return p;
}; Type guard
import type {CornerPinParams, CornerPinUvCoordinate} from '@remotion/effects';
const isUvCoordinate = (v: unknown): v is CornerPinUvCoordinate =>
Array.isArray(v) &&
v.length === 2 &&
v.every((n) => typeof n === 'number' && Number.isFinite(n));
const isCornerPinParams = (p: unknown): p is CornerPinParams => {
if (typeof p !== 'object' || p === null) return false;
const rec = p as Record<string, unknown>;
for (const k of ['topLeft', 'topRight', 'bottomRight', 'bottomLeft']) {
if (rec[k] !== undefined && !isUvCoordinate(rec[k])) return false;
}
return true;
}; Prevention
- Annotate corner params as readonly [number, number] at every data boundary so the compiler rejects objects and wrong-arity arrays.
- When importing coordinates from external sources, run them through an isUvCoordinate guard before constructing the params object.
- Never animate corner values with a Spring/interpolate that can yield NaN; clamp with Number.isFinite.
When it happens
Trigger: Calling cornerPin() with topLeft: {x: 0, y: 0} (object instead of array), topLeft: [0] or topLeft: [0, 0, 0] (wrong arity), topLeft: [0, '1'] (string element), topLeft: [NaN, 0] or topLeft: [Infinity, 0] (non-finite), or topLeft: null (null is not undefined and fails Array.isArray). The check runs inside validateCornerPinParams, which the effect invokes before setup.
Common situations: Authoring a corner-pin animation from an external data source whose coordinates come back as objects; serializing tuples through JSON that mutated into objects; passing the output of a math library that returns BigNumbers or strings; copy-pasting a 3-tuple from a 3D tool; animating values through a Spring whose interpolation momentarily yields NaN.
Related errors
- outputRange tuples must contain at least 1 number
- outputRange tuples must all have the same length, but got ${
- "${name}" must be >= -1, but got ${JSON.stringify(value)}
- "${name}" must be greater than 0, but got ${JSON.stringify(v
- "${name}" must be a boolean, but got ${JSON.stringify(value)
AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12).
Data as JSON: /api/errors/c885f4536edaeefe.
Report an issue: GitHub.