greensock/GSAP · warning

GSDevTools error: invalid animation.

Error message

GSDevTools error: invalid animation.

What it means

GSDevTools validates the animation passed to it (or via animation()) is an actual GSAP Animation instance. The value may be a string ID, in which case _getAnimationById is used to resolve it; if after resolution it is still not an Animation instance, this warning is printed. The library throws it because the scrubber/controls cannot operate on a non-animation object.

Source

Thrown at src/GSDevTools.js:756

					matches = (i && animations[i].vars.id === animations[i-1].vars.id) ? matches + 1 : 0;
					option.setAttribute("value", (option.innerHTML = animations[i].vars.id + (matches ? " [" + matches + "]" : (animations[i+1] && animations[i+1].vars.id === animations[i].vars.id) ? " [0]" : "")));
				}
				for (; i < options.length; i++) {
					list.removeChild(options[i]);
				}
			},
			animation = function(anim) {
				let ts = parseFloat(timeScale.options[timeScale.selectedIndex].value) || 1,
					tl, maxDuration;
				if (!arguments.length) {
					return selectedAnimation;
				}
				if (_isString(anim)) {
					anim = _getAnimationById(anim);
				}
				//console.log("animation() ", anim.vars.id);
				if (!(anim instanceof Animation)) {
					console.warn("GSDevTools error: invalid animation.");
				}
				if (anim.scrollTrigger) {
					console.warn("GSDevTools can't work with ScrollTrigger-based animations; either the scrollbar -OR- the GSDevTools scrubber can control the animation.");
				}
				if (anim === selectedAnimation) {
					return;
				}
				if (selectedAnimation) {
					selectedAnimation._inProgress = inProgress;
					selectedAnimation._outProgress = outProgress;
				}
				selectedAnimation = anim;
				if (linkedAnimation) {
					ts = linkedAnimation.timeScale();
					if (linkedAnimation._targets && linkedAnimation._targets[0] === declaredAnimation) {
						declaredAnimation.resume();
						linkedAnimation.kill();
					}

View on GitHub (pinned to 13e2b79054)

Solutions

  1. Pass the actual gsap.timeline()/gsap.to() instance to `animation:` in GSDevTools.create()
  2. If using a string, set `gsap.timeline({id: "myId"})` and pass exactly that id string
  3. Create the animation before GSDevTools.create(), or pass a function/later reference supported by your version
  4. Ensure only one copy of gsap is loaded so instanceof checks pass

Example fix

// before
GSDevTools.create({ animation: "myTween " }); // typo, id not found -> undefined
// after
gsap.timeline({ id: "myTween" }).to(".box", {x: 100});
GSDevTools.create({ animation: "myTween" });
Defensive patterns

Strategy: type-guard

Validate before calling

const anim = typeof id === 'string' ? gsap.getById(id) : id;
if (!anim || typeof anim.play !== 'function' || !(anim instanceof gsap.core.Animation)) {
  throw new Error('GSDevTools: animation must be a GSAP Animation instance or registered id');
}

Type guard

function isGSAPAnimation(a) {
  return a instanceof gsap.core.Animation || (a && typeof a.play === 'function' && typeof a.progress === 'function');
}

Prevention

When it happens

Trigger: Passing GSDevTools.create() an `animation` value that is undefined, a timeline not yet created, a misspelled string id, or a non-GSAP object (e.g. a raw DOM element, a Promise, or an animation from a different/older GSAP core).

Common situations: Typos in the animation `id`; referencing an animation created after GSDevTools.create() runs; passing the result of a function that returns undefined; bundlers loading two copies of gsap so instanceof Animation fails.

Related errors


AI-assisted analysis of greensock/GSAP@13e2b79054 (2026-08-29). Data as JSON: /api/errors/ade701335131b368. Report an issue: GitHub.