remotion-dev/remotion · error · TypeError

"stops" must be <= ${MAX_STOPS}, but got ${JSON.stringify(st

Error message

"stops" must be <= ${MAX_STOPS}, but got ${JSON.stringify(stops)}

What it means

Thrown by validateExposureParams() in exposure.ts when the resolved `stops` value exceeds MAX_STOPS (5). It is a TypeError raised during validateParams, before WebGL setup, giving deterministic feedback. The value is JSON-stringified into the message.

Source

Thrown at packages/effects/src/exposure.ts:62

};

const resolve = (params: ExposureParams): ExposureResolved => ({
	stops: params.stops ?? DEFAULT_STOPS,
});

const validateExposureParams = (params: ExposureParams): void => {
	assertEffectParamsObject(params, 'Exposure');
	assertOptionalFiniteNumber(params.stops, 'stops');

	const {stops} = resolve(params);
	if (stops < MIN_STOPS) {
		throw new TypeError(
			`"stops" must be >= ${MIN_STOPS}, but got ${JSON.stringify(stops)}`,
		);
	}

	if (stops > MAX_STOPS) {
		throw new TypeError(
			`"stops" must be <= ${MAX_STOPS}, but got ${JSON.stringify(stops)}`,
		);
	}
};

const VERTEX_SHADER = /* glsl */ `#version 300 es
in vec2 aPos;
in vec2 aUv;
out vec2 vUv;

void main() {
	vUv = aUv;
	gl_Position = vec4(aPos, 0.0, 1.0);
}
`;

const FRAGMENT_SHADER = /* glsl */ `#version 300 es
precision highp float;

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Clamp to the documented range before calling: exposure({ stops: Math.max(-5, Math.min(5, value)) }).
  2. Replace overshooting interpolation (spring/bounce) with an easing that respects the [−5, 5] bounds.
  3. Constrain upstream input (data file, schema, slider) so stops can never exceed 5.
  4. Stack multiple exposure() effects if a larger combined shift is required.

Example fix

// before
exposure({ stops: animatedValue }); // animatedValue peaks at 6.2 -> throws

// after
exposure({ stops: Math.min(5, Math.max(-5, animatedValue)) });
Defensive patterns

Strategy: validation

Validate before calling

const EXPOSURE_STOPS_MIN = -5;
const EXPOSURE_STOPS_MAX = 5;

const clampStops = (stops: number): number =>
  Math.max(EXPOSURE_STOPS_MIN, Math.min(EXPOSURE_STOPS_MAX, stops));

// usage: exposure({ stops: clampStops(animatedValue) })

Type guard

const isExposureStops = (v: unknown): v is number =>
  typeof v === 'number' &&
  Number.isFinite(v) &&
  v >= -5 &&
  v <= 5;

// guard: if (isExposureStops(value)) exposure({ stops: value }); else /* clamp or skip */

Prevention

When it happens

Trigger: Calling exposure({ stops: x }) with x > 5 (e.g. exposure({ stops: 8 })). Because resolve() only defaults undefined to 0, any explicitly provided number above 5 reaches this branch and throws.

Common situations: Easing curves that overshoot past +5 (springs are a common culprit); data-driven stops from a config with no upper bound; composing exposures additively and assuming the effect clamps; UI controls allowing values beyond the documented ±5.

Related errors


AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12). Data as JSON: /api/errors/4ca01b24482c0d67. Report an issue: GitHub.