Nubu
  • Features
  • How it works
  • Pricing
  • Integrations
  • FAQ
  • Contact
  • Docs
  • Blog
  • Log in
  • Log inSign up
    Nubu

    Creative automation for teams that ship campaigns, not busywork.

    Try for free
    Product
    FeaturesNubu MotionHow it worksPricingIntegrationsFAQ
    Company
    AboutContactDocsBlogLog in
    Legal
    Privacy policyTerms of serviceData deletion
    © 2026 Bear Studios · Nubu
    Introduction
    Templates overviewInstalling Nubu BuilderAdding template compsAdding propertiesAdding footageSetting defaultsExporting your templateUploading a template
    Flows overviewAvailable nodesAdding a nodeConnecting nodesWorking with dataTemplates and outputsUsing AI nodesBuilding a creative
    CampaignsTemplatesAssetsGlossariesRendersSettings
    Connecting Meta AdsConnecting Google AdsConnecting OpenAIConnecting Gemini AI StudioConnecting Anthropic
    AI Assistant overviewPrompt examplesAssistant settings and controls
    Nubu Motion scripts: the reference
    OverviewQuick startDifferences from After EffectsDeterminism
    JavaScript supportReferences and idsProject scriptsState and timeBudgets and errorsFormatting values
    GlobalTime conversionInterpolationVector mathsRandom and noiseColour conversionOther maths
    LayerCompPropertyKeyframePathGeometryProjectComponentConsoleComponentControlExpressionControl
    The property vocabularyAnimators propertiesAudio propertiesContent propertiesContents propertiesEffects propertiesExpression controls propertiesFills propertiesInstance controls propertiesLayer propertiesLayout propertiesMasks propertiesStrokes propertiesText style propertiesTransform propertiesTrim path properties
    Error codesBudgets
    Changelog
    All docs
    Motion Scripting
    / Objects

    Keyframe

    Reading a property's authored keyframes: frame, value, interpolation and handles, exactly as the timeline stores them.

    M10 DOM2-2: one keyframe, READ-ONLY - the get_keyframes WIRE SHAPE verbatim per entry (frame, value in the property's wire kind, interpolation "linear" | "bezier" | "hold", bezier in/out {x, y} handles when present, spatial tangents on spatial tracks - any future wire member lands here by the same rule) plus the derived time in seconds. Scripts never WRITE keys (the E25 model: the overlay, never the document).

    Table of contentsframeWorked example: Keys sit on the frame gridtimeWorked example: Frames into secondsvalueWorked example: Reading a key's stored valueinterpolationWorked example: Linear, bezier or hold

    frame#

    Type: number (accessor).

    The canonical grid frame (integer).

    Determinism: pure.

    A Keyframe is the get_keyframes wire entry verbatim plus the derived time: frame, value in the property's wire kind, interpolation, bezier in and out handles where the key has them, spatial tangents on spatial tracks. Any future wire member lands here by the same rule, which is how the surface stays open ended. Scripts never write keys: set() drives the overlay, the document's tracks stay untouched.

    Worked example: Keys sit on the frame grid#

    The scene. One pale card near the centre with its rotation keyframed from 0 to 90 degrees across the comp, for scripts that read and reshape keyframed motion.

    • The comp Keyed: 640 by 360 at 25 fps, 100 frames (4 seconds).
    • Card: a solid layer, at [320, 180], 120 by 80, filled #e8e6e3.
      • Keyframe transform.rotation at frame 0: 0 (linear).
      • Keyframe transform.rotation at frame 96: 90 (linear).

    The script. Add a project script named Frame reader and paste:

    export const outputs = ["layer_1:transform.opacity"];
    
    export function frame() {
      const last = prop("Card:transform.rotation").keyAt(1);
      set("layer_1:transform.opacity", last.frame - 26);
    }
    

    Line by line:

    • Line 4: The second key of the rotation track, as the wire stores it.
    • Line 5: Its frame member is the canonical grid integer 96, driving the opacity to 70.

    What you see. The card holds 70 percent opacity, read straight off the last key's frame number.

    The card holds 70 percent opacity, read straight off the last key's frame number.

    time#

    Type: number (accessor).

    frame / fps, in seconds (derived).

    Determinism: pure.

    Worked example: Frames into seconds#

    The scene. One pale card near the centre with its rotation keyframed from 0 to 90 degrees across the comp, for scripts that read and reshape keyframed motion.

    • The comp Keyed: 640 by 360 at 25 fps, 100 frames (4 seconds).
    • Card: a solid layer, at [320, 180], 120 by 80, filled #e8e6e3.
      • Keyframe transform.rotation at frame 0: 0 (linear).
      • Keyframe transform.rotation at frame 96: 90 (linear).

    The script. Add a project script named Time reader and paste:

    export const outputs = ["layer_1:transform.opacity"];
    
    export function frame() {
      const last = prop("Card:transform.rotation").keyAt(1);
      const in_window = last.time > 3.8 && last.time < 3.9;
      set("layer_1:transform.opacity", in_window ? 75 : 5);
    }
    

    Line by line:

    • Lines 4-5: time is the derived member: frame divided by the comp's fps. Frame 96 at 25 fps is 3.84 seconds, comfortably inside the window.
    • Line 6: The proof value drives only while the derived seconds agree with the grid.

    What you see. The card rests at 75 percent opacity: the last key's derived time landed on 3.84 seconds.

    The card rests at 75 percent opacity: the last key's derived time landed on 3.84 seconds.

    value#

    Type: T (accessor).

    The key's value in the property's wire kind.

    Determinism: pure.

    Worked example: Reading a key's stored value#

    The scene. One pale card near the centre with its rotation keyframed from 0 to 90 degrees across the comp, for scripts that read and reshape keyframed motion.

    • The comp Keyed: 640 by 360 at 25 fps, 100 frames (4 seconds).
    • Card: a solid layer, at [320, 180], 120 by 80, filled #e8e6e3.
      • Keyframe transform.rotation at frame 0: 0 (linear).
      • Keyframe transform.rotation at frame 96: 90 (linear).

    The script. Add a project script named Value reader and paste:

    export const outputs = ["layer_1:transform.opacity"];
    
    export function frame() {
      const last = prop("Card:transform.rotation").keyAt(1);
      set("layer_1:transform.opacity", last.value + 5);
    }
    

    Line by line:

    • Line 4: The key's value arrives in the property's wire kind: a scalar 90 for this rotation key. Points arrive as [x, y], colours as their wire shape, exactly as get_keyframes stores them.
    • Line 5: The stored target drives another property: read and react.

    What you see. The card brightens to 95 percent opacity, five over the last key's stored 90 degrees.

    The card brightens to 95 percent opacity, five over the last key's stored 90 degrees.

    interpolation#

    Type: string (accessor).

    "linear" | "bezier" | "hold" (the wire spelling).

    Determinism: pure.

    Worked example: Linear, bezier or hold#

    The scene. One pale card near the centre with its rotation keyframed from 0 to 90 degrees across the comp, for scripts that read and reshape keyframed motion.

    • The comp Keyed: 640 by 360 at 25 fps, 100 frames (4 seconds).
    • Card: a solid layer, at [320, 180], 120 by 80, filled #e8e6e3.
      • Keyframe transform.rotation at frame 0: 0 (linear).
      • Keyframe transform.rotation at frame 96: 90 (linear).

    The script. Add a project script named Interpolation reader and paste:

    export const outputs = ["layer_1:transform.opacity"];
    
    export function frame() {
      const first = prop("Card:transform.rotation").keyAt(0);
      set("layer_1:transform.opacity", first.interpolation === "linear" ? 80 : 10);
    }
    

    Line by line:

    • Lines 4-5: interpolation carries the wire spelling: linear, bezier or hold. The fixture's first key is linear, so the proof value drives. A bezier key would also carry its in and out handles on the same object.

    What you see. The card sits at 80 percent opacity because the first rotation key reports linear interpolation.

    The card sits at 80 percent opacity because the first rotation key reports linear interpolation.