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.
| Field | Meaning |
|---|---|
Type | number, string, boolean, Color3, Vector2, Vector3, CFrame, UDim, UDim2, NumberRange, Rect, Instance, Any, or "Enum.<EnumType>" such as "Enum.EasingStyle" |
Default | The starting value. Must match Type |
Keyframable | Whether the editor offers keyframes for it |
Min, Max | Limits for a number |
Description | Shown 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:
| Hook | Called 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
| Member | Meaning |
|---|---|
context.Subject | The 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
ModuleScriptin the Explorer and click Import Selected Module, or click Import File... and pick a.luaufile. The module is validated first, and if it doesn't conform, the editor lists every problem. - By hand: put the
ModuleScriptinReplicatedStorage.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
Enterwith a non-zero offset. - Stop the preview partway through and check that
Exitcleaned up everythingEntermade. - 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.