After Effects Expressions: Choosing Between Legacy ExtendScript and the JavaScript Engine
After Effects stores its expression engine per project. Here is how to tell which one you are on, what breaks when you switch, and a migration order that keeps renders matching.
10 Jul 2026, 15:18 UTC

The symptom: an expression that works in one project and fails in another
You paste a downloaded expression onto a layer and After Effects rejects it with a syntax error. You paste the same expression into a different project and it runs. Nothing about the layer changed — but the project's expression engine did.
After Effects ships two expression engines, and the choice is stored per project, not per expression. Legacy ExtendScript is the original engine, built on an old ECMAScript dialect. The JavaScript engine is built on V8 and accepts modern syntax. Knowing which one a project is on, and what changes when you switch, is the difference between a five-minute fix and an afternoon of rewriting.
What actually differs between the two engines
The clearest difference is syntax. Legacy ExtendScript predates ES6, so it does not accept let, const, arrow functions, template literals, or default parameters. The JavaScript engine accepts all of them. That matters little for a one-line wiggle expression and a lot for anything data-driven, where const and array helpers keep the code readable.
The second difference is the global environment. Expressions in both engines can reach layer and composition data, but some legacy-only globals and ExtendScript-specific helpers do not carry over cleanly. This is the part worth testing rather than assuming: the set of available globals is not identical, and a template that leans on an old helper can fail after a switch even though its syntax is valid. Treat any expression you did not write as unverified until you run it.
| Area | Legacy ExtendScript | JavaScript engine |
|---|---|---|
| Syntax level | Pre-ES6 | ES6 and later accepted |
| Scope of the setting | Project-wide | Project-wide |
| Third-party templates | Often written for this engine | May need rewriting |
| Performance | Workload-dependent | Workload-dependent; measure locally |
A worked example: the same label logic in both engines
Suppose a text layer should cycle through three labels, changing every two seconds, and hold on the last one. Run this on the text layer's Source Text property.
Legacy-safe version:
var labels = ["Intro", "Body", "Outro"];
var i = Math.floor(time / 2);
if (i > labels.length - 1) { i = labels.length - 1; }
labels[i];
Modern version, same behavior:
const labels = ["Intro", "Body", "Outro"];
const i = Math.min(Math.floor(time / 2), labels.length - 1);
labels[i];
The second form is shorter and easier to extend — add a fourth label and nothing else changes. It also fails immediately on the Legacy engine, which makes it a useful probe: paste it into a property and see whether it evaluates.
The trade-off: it is a project-wide switch
You cannot run one expression on Legacy and another on JavaScript inside the same project. That all-or-nothing behavior is the real cost. If a project depends on one legacy-only template, switching the whole project to JavaScript means either rewriting that template or leaving the project on Legacy.
Performance is the other variable, and it is not a clean win either way. Complex logic and heavy array work tend to benefit from the modern engine, while large projects can pay a parsing cost when expressions are first evaluated. The honest answer is that the delta depends on your expressions and your machine, so measure it rather than trusting a general claim.
How to check and how to measure
- Open File > Project Settings and look for the Expressions Engine option. The exact tab and wording vary by release; confirm what your installed version shows.
- Paste an arrow function such as
const f = () => 1; f();into a property expression. A syntax error means the Legacy engine is active. - To compare speed, duplicate the comp, set each project copy to a different engine, purge the disk cache between renders, and render the same frame range on the same machine. Compare total render time, not a single frame.
Version note: the engine option and its behavior have changed across After Effects releases. Verify the setting exists and behaves as described here in your installed version before committing a production project to it.
A migration order that reduces breakage
- Save a copy of the project before switching. Switching back is the rollback, but expressions saved with modern syntax will break if you return to Legacy.
- List every expression in the project. The expression engine field in the timeline, or a script, surfaces them faster than clicking through layers.
- Switch the engine, then step through the comps and compare each expression's result against the pre-switch render. A frame-by-frame comparison catches silent behavior changes that a syntax check misses.
- Rewrite failures one at a time, replacing
varwithconstonly where the value genuinely does not change.
If the project is stable and shipping, there is no prize for migrating. Switch when you need the syntax or the maintainability, and keep the old copy until the new render matches.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.