Skip to main content

Section Types

Every built-in section type is written against the same public API you use for your own, so a custom type is a peer of Property and Animation rather than a plugin to them. It appears in the editor's menus, gets an Inspector built from its parameters, keyframes, blends, scrubs, and plays in game like any other.

The shape of a section type​

A section type is a ModuleScript that returns a table:

return {
Name = "Pulse", -- unique, identifier-like
Capability = "Sampled", -- or "Triggered"
SubjectClass = "BasePart", -- the class the subject must be; nil for no requirement
Description = "Pulses the subject's Transparency.",
Parameters = {
Intensity = { Type = "number", Default = 1, Keyframable = true, Min = 0, Max = 1 },
},
-- Sampled: Evaluate. Triggered: Enter, Exit, and optionally Update and Seek.
}

The full contract is SectionTypeModule. Two optional fields narrow where the type is offered: TrackType restricts it to one track type, and PlaysSequence gives it a child runtime for the sequence it targets, as the built-in Subsequence section uses.

Parameters​

Parameters are declared, not drawn. The editor reads the declarations and builds the Inspector rows itself.

FieldMeaning
Typenumber, string, boolean, Color3, Vector2, Vector3, CFrame, UDim, UDim2, NumberRange, Rect, Instance, Any, or "Enum.<EnumType>" such as "Enum.EasingStyle"
DefaultThe starting value. Must match Type
KeyframableWhether the editor offers keyframes for it
Min, MaxLimits for a number
DescriptionShown in the Inspector

Read a parameter with context:GetParameter(key, localTime). For a keyframed parameter, pass the time to sample it there; for a constant one, the time is ignored.

Sampled sections​

A sampled section is a pure function of time. It implements Evaluate(context, localTime) and returns the properties to write on the subject at that moment. Because it holds no state, it scrubs forwards and backwards for free, and Flow handles restoring, holding, and blending for it.

local PulseSection = {
Name = "Pulse",
Capability = "Sampled",
SubjectClass = "BasePart",
Description = "Pulses the subject's Transparency.",
Parameters = {
Intensity = { Type = "number", Default = 1, Keyframable = true, Min = 0, Max = 1 },
Frequency = { Type = "number", Default = 2, Keyframable = false, Min = 0.01 },
},
}

function PulseSection.Evaluate(context, localTime: number): { [string]: any }
local intensity = context:GetParameter("Intensity", localTime)
local frequency = context:GetParameter("Frequency")
local wave = (math.sin(localTime * frequency * math.pi * 2) + 1) / 2
return { Transparency = wave * intensity }
end

return PulseSection

localTime is seconds since the section's start. Prefer a sampled section whenever the effect can be expressed as "the value at time t".

Triggered sections​

A triggered section starts something that runs on its own clock, such as an animation track, a sound, or an emitter. It implements hooks that Flow calls as the playhead moves:

HookCalled when
Enter(context, offset)The playhead enters the section; offset is how far in it landed
Update(context, localTime)Every frame while the playhead is inside
Exit(context)The playhead leaves, or the playback stops early
Seek(context, localTime)The playhead jumps within the section

Enter and Exit are required. Without a Seek, a jump runs Exit and then Enter at the new offset; implement it when the effect can jump in place, as the Animation and Audio sections do with TimePosition.

Every section must handle starting at any offset, not just at zero. A player who joins a synced playback halfway through enters each section partway in, exactly as a scrub does.

local HighlightSection = {
Name = "Highlight",
Capability = "Triggered",
SubjectClass = "Model",
Description = "Outlines the subject with a keyframed glow.",
Parameters = {
Color = { Type = "Color3", Default = Color3.new(1, 0.8, 0.2), Keyframable = false },
Intensity = { Type = "number", Default = 1, Keyframable = true, Min = 0, Max = 1 },
},
}

function HighlightSection.Enter(context, _offset: number)
local highlight = Instance.new("Highlight")
highlight.FillColor = context:GetParameter("Color")
highlight.OutlineColor = context:GetParameter("Color")
highlight.Parent = context.Subject
context.State.Highlight = highlight
end

function HighlightSection.Update(context, localTime: number)
local highlight = context.State.Highlight
if highlight then
-- GetWeight applies the section's blend ramps and weight
local strength = context:GetParameter("Intensity", localTime) * context:GetWeight(localTime)
highlight.FillTransparency = 1 - strength * 0.5
highlight.OutlineTransparency = 1 - strength
end
end

function HighlightSection.Exit(context)
if context.State.Highlight then
context.State.Highlight:Destroy()
context.State.Highlight = nil
end
end

return HighlightSection

context.State is a table the section can keep anything in for the life of the playback.

Writing properties from a triggered section​

A triggered section that changes the subject's own properties should write them with context:SetProperty(instance, property, value) rather than assigning them directly. Writes made that way are remembered and restored or held by the section's restore policy, join blending with the section's priority, and are kept out of record mode in the editor. An instance the section creates and destroys itself, like the Highlight above, doesn't need it.

Other context members​

MemberMeaning
context.SubjectThe instance the track resolved to
context:GetSequenceParameter(key)A sequence parameter, including per-play overrides
context:IsPlaying()False while paused or scrubbing. Skip one-shot effects, such as a burst, when it is false
context:GetDuration()The section's length in seconds
context:GetTimeScale()Playback rate times time dilation, for a section that runs a clock of its own
context:GetWeight(localTime)The section's blend weight, for a triggered section to apply to its medium

The full list is SectionContext.

Installing a section type​

  • From the editor: with nothing selected, find Custom Section Types in the Inspector. Select the ModuleScript in the Explorer and click Import Selected Module, or click Import File... and pick a .luau file. The module is validated first, and if it doesn't conform, the editor lists every problem.
  • By hand: put the ModuleScript in ReplicatedStorage.Flow.CustomSections. The executor loads it when it starts, and warns about any that don't conform.

A sequence that uses a section type the place doesn't have still plays. Sections of that type do nothing, and the editor lists the missing type under the sequence's warnings.

A complete example, examples/PulseSection.luau, ships in the repository.

Testing a section type​

  • Scrub through it backwards and forwards in the editor. A sampled section should look identical either way.
  • Start the preview from the middle of the section to check Enter with a non-zero offset.
  • Stop the preview partway through and check that Exit cleaned up everything Enter made.
  • If a hook errors, Flow disables that section for the rest of the playback or preview and warns with the error, instead of taking the whole sequence down. Check the Output window when a section goes quiet.