An After Effects-style rule attached to a keyframed property: make a move springy, bounce it, jitter it, loop it, or drive it from your own formula — including from another layer’s animation. Your keyframes are never rewritten, so switching an expression off restores the plain motion instantly.
Add one
In the Keyframe Builder, hover a property row and a small ƒx button appears next to its name. Click it to pick an expression; the button stays lit violet while one is active, so you can see at a glance which properties are driven.
Expressions work on every numeric track — layer transforms and filters, Overlay Effects parameters, Camera Layer keyframes, Scene Rig channels — but not on colours or shape outlines. Remove expression turns it off, and deleting the track removes its expression with it.
The ready-made ones

Elastic makes the move itself springy: the layer launches off the previous key, shoots past the target and wobbles into place, all as one continuous motion.
- Strength mixes between the plain keyed motion (0%) and the full spring (100%, or higher to exaggerate).
- Wobbles is how many wobbles happen across the move. Fewer gives a soft landing with a single late overshoot; more gives a snappy arrival that rings.
- Decay is how fast the wobbling dies out.
Bounce drops the move onto its target like a ball and rebounds in decaying hops, settling exactly on the key. Bounciness decides how much of the move is spent hopping (bouncier lands sooner and hops longer), Max Bounces caps the hops. It never passes the target.
Wiggle adds continuous smooth jitter around the value across the whole clip, like AE’s wiggle(). Unlike the other two it leaves the underlying motion alone. Amplitude is in the property’s own units, Frequency in Hz. A single-key (constant) track plus Wiggle is the quickest handheld or idle motion you can make.
Elastic and Bounce shape the motion between keys, so on that track they take over from the keys’ own easing — dial Strength down to blend back toward it. Both are timed to each move, so the same settings read identically on a half-second pop and a five-second drift, and they work on a plain two-key track spanning the whole clip.
Custom expressions
Custom lets you write the value yourself. Whatever the expression works out to becomes the property’s value, so value is where you start when you want to build on the keyframed motion:
value + wiggle(2, 0.1)
value * (1 + 0.05 * sin(time * 6.28))
layer("Logo").scale * 0.5
loopOut("pingpong")

The Insert menu above the editor drops things at your cursor, so you rarely have to remember a name. Select everything first and an insert replaces the whole expression. Under the box you always see what the expression works out to at the playhead, or the mistake if it has one — and while it is broken the property simply keeps its keyframed value, so nothing on the canvas breaks while you type.
| Values | value (the keyframed value), time, frame, progress, duration, fps — all counted inside the clip, so time is 0 at the clip’s start, not the project’s |
| Math | + – * / %, ^ for powers, comparisons, a ? b : c, and sin cos tan abs floor ceil round sqrt min max pow (or Math.sin — either works), plus PI |
| Handy | clamp(value, 0, 1), lerp(from, to, t), linear(time, 0, duration, 0, 100) to remap a range, ease(…) for a softened version |
| Motion | wiggle(freq, amount) for jitter around the value, noise(time), random(min, max) |
| Looping | loopOut(), loopIn(), loopOutDuration(type, seconds) |
| This track | valueAtTime(t) for the value at another moment, numKeys |
| Other layers | layer(“Logo”).scale, thisLayer.opacity |
| Inside a sequence | layer(“My Sequence”).layer(“Inner”).opacity |
Comments (// and /* */) are allowed, and an expression is a single formula — there are no variables or multi-line programs.
A broken expression turns its ƒx button red, and stays visible even when the row isn’t hovered, so you can spot the problem row at a glance without opening anything — hover it and the tooltip tells you what’s wrong. Half-finished (value +), a name that doesn’t exist, a layer that isn’t there, and a result that isn’t a number all count. The button goes back to violet the moment the expression is valid again.
Looping keyframes
Animate one cycle and let loopOut() repeat it for the rest of the clip instead of copying keyframes:
loopOut()— start the animation again (this is"cycle", the default).loopOut("pingpong")— play it backwards, then forwards, back and forth.loopOut("offset")— stack each repeat on the end of the last, so a move keeps travelling instead of jumping back.loopOut("continue")— don’t repeat at all; carry on at the speed of the last keyframe.
loopIn(…) does the same before your first keyframe. A second number loops only the last few keys — loopOut("cycle", 1) repeats just the final segment — and loopOutDuration("cycle", 1) takes seconds instead of a keyframe count. Every mode joins the animation smoothly at the boundary key, so a loop never pops.
Reading another layer
layer("Logo").scale gives you what that layer’s property is animating to at the same instant, which is how you make one layer follow another without parenting: opacity that tracks a logo’s scale, a shadow that mirrors a rotation, a counter driven by a slider layer.
Once an expression names a layer, a small Select “…” badge appears under the editor for each one — click it to jump straight to that layer, which saves hunting for it on the timeline when you want to check or adjust what you are reading from. Badges only appear for layers you can actually go to: nothing shows for thisLayer, for a layer you name that is the one you are already editing, or for a name that matches no layer. A chain into a sequence offers the sequence itself, since that is the layer on this timeline.
You can only name layers that are on screen at the same time as this one — a layer elsewhere on the timeline has no value to read. Names are the layer’s custom name, a text layer’s content, or the clip name, and the Insert menu lists every eligible layer with the properties it animates, so picking from the menu always produces a correct reference.
A layer that doesn’t animate the property you ask for reports its fixed value instead. Misspell a property name and you are told which name is wrong, rather than quietly getting a zero.
Reading inside a sequence
A Sequences and Presets keeps its own timeline inside it, and you can reach a layer in there by chaining:
layer("My Sequence").layer("Inner Star").opacity
The value is what that inner layer genuinely shows at that moment, including any trimming or speed change applied to the sequence itself. Chain again for a sequence inside a sequence. The Insert menu lists inner layers under their container’s name (Intro Seq › Inner Star), so you don’t have to type either name.
Repeatable and safe
Anything random — random(), noise(), wiggle(), and the Wiggle expression — is deterministic. The same frame always produces the same number, so a render matches the preview exactly and a re-render matches the first one. It is seeded per layer and property, so two copies of a layer jitter independently instead of in lockstep.
Expressions are read and calculated by the app, never run as code. Opening a shared project cannot execute anything hidden inside one.
Everything here is evaluated by the same engine the renderer uses, so the preview, the overlays and the final export always agree.
Where to go next
- Keyframe Builder — the tracks, keys and easings expressions sit on top of
- Graph Editor and Custom Easing — hand-shaped curves for a single segment
- Sequences and Presets — the containers you can read into
- Agent-Driven Editing — expressions an agent can write for you
Leave a Reply