Reference Verse

Option Chaining in Verse: Safely Unwrapping ?Values

Half the crashes and 'Unknown identifier' headaches in early Verse projects come from mishandling option types — those `?agent`, `?fort_character`, `?creative_prop` values that might be empty. Option chaining is the pattern for unwrapping them safely so your vault doors, granters and cinematics only fire on real data.

Updated
The code on this reference page is provided as-is and did not pass the latest compile check — treat the examples as a starting point and verify in your project.
Watch the Knotoption_chaining in ~90 seconds.

Overview

An option in Verse is a value that may or may not be there. You write its type with a leading ?: ?agent, ?fort_character, ?creative_prop. Think of it as a box that is either empty (called false) or holds exactly one value.

You meet options constantly in Fortnite game code:

  • A listenable(?agent) event hands your handler an Agent : ?agent — the player might be null (e.g. environmental damage with no attacker).
  • An elimination_result has EliminatingCharacter : ?fort_character — the eliminator may not exist.
  • Map[Key] lookups, Array[Index] access, and any <decides> call produce optional / failable results.

Option chaining is the discipline of unwrapping these safely before you touch the value. The core tool is the ? unwrap operator used inside a failure context (if, for, set ... :=). Value? succeeds and gives you the inner value if the box is full, and fails (skips the branch) if it's empty. This turns "might crash / might be garbage" into "only run when the data is real."

Reach for option chaining whenever:

  • You subscribe to a device event that passes a ?agent.
  • You store a device or prop reference that could be unset.
  • You want one guarded expression instead of nested null checks.

Because option chaining is a language feature, not a device, this article grounds its examples in real devices — button_device, trigger_device, and creative_prop — so you see the pattern in a working game scenario.

API Reference

(API surface could not be resolved for this device.)

Walkthrough

Scenario: A player presses a button. We grant them a "key" by showing a hidden creative_prop vault door prop and moving it — but ONLY if we can safely unwrap the pressing agent and confirm our prop reference is valid. This shows the whole option-chaining loop: an event gives us a ?agent, and we hold a prop we must validate before using.

using { /Fortnite.com/Devices }
using { /Fortnite.com }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }

# A device that opens a vault when a button is pressed by a real player.
vault_controller := class(creative_device):

    # The button the player interacts with.
    @editable
    OpenButton : button_device = button_device{}

    # The prop we treat as the vault door. Starts hidden.
    @editable
    VaultDoor : creative_prop = creative_prop{}

    OnBegin<override>()<suspends>:void =
        # Hide the door until someone earns it.
        VaultDoor.Hide()
        # Subscribe our handler to the button's interaction event.
        OpenButton.InteractedWithEvent.Subscribe(OnButtonPressed)

    # InteractedWithEvent hands us Agent : ?agent — it MIGHT be empty.
    OnButtonPressed(Agent : ?agent) : void =
        # Option chaining: unwrap Agent with ?. If empty, the whole
        # block is skipped and nothing happens.
        if (Player := Agent?):
            OpenVaultFor(Player)

    # Now we have a real, non-optional agent.
    OpenVaultFor(Player : agent) : void =
        # Guard the prop too: IsValid is a <decides> call that fails
        # if the prop was destroyed. Chain it into the same if.
        if (VaultDoor.IsValid[]):
            VaultDoor.Show()
            # Nudge it upward so it visibly slides open.
            VaultDoor.SetAngularVelocity(vector3{Z := 1.0})

Line by line:

  • @editable OpenButton : button_device / VaultDoor : creative_prop — the two placed things we control. They MUST be @editable fields on a creative_device class, or Verse can't call their methods.
  • VaultDoor.Hide() in OnBegin hides the prop at match start (a real creative_prop method).
  • OpenButton.InteractedWithEvent.Subscribe(OnButtonPressed) wires the button press to our method.
  • OnButtonPressed(Agent : ?agent) — the signature the event demands. Agent is an option.
  • if (Player := Agent?): — the heart of option chaining. Agent? unwraps the box. If a player is inside, Player becomes a real agent and the branch runs. If the box is empty (no player), the branch is skipped safely — no crash.
  • if (VaultDoor.IsValid[]):IsValid is a failable <decides> method (note the [] failable call brackets). If the prop still exists, we proceed; otherwise we skip. This is option/failure chaining on a device method.
  • VaultDoor.Show() and SetAngularVelocity(...) — real prop methods that make the door appear and spin open.

The key takeaway: two potentially-missing things (the agent and the prop) are each unwrapped in a failure context before use.

Common patterns

Pattern 1 — Unwrapping a ?agent from a trigger

A trigger_device's TriggeredEvent also passes ?agent. Same unwrap, different device.

using { /Fortnite.com/Devices }
using { /Fortnite.com }
using { /Verse.org/Simulation }

trap_plate := class(creative_device):

    @editable
    Plate : trigger_device = trigger_device{}

    @editable
    TrapProp : creative_prop = creative_prop{}

    OnBegin<override>()<suspends>:void =
        Plate.TriggeredEvent.Subscribe(OnStepped)

    OnStepped(Agent : ?agent) : void =
        # Only spring the trap if a real player stepped on the plate.
        if (Player := Agent?):
            # A real agent exists; make the trap prop drop by hiding it.
            TrapProp.Hide()

Pattern 2 — Chaining through elimination_result.EliminatingCharacter

Game structs expose options too. Here we read the optional eliminator off an elimination and only score if it exists.

using { /Fortnite.com/Devices }
using { /Fortnite.com }
using { /Fortnite.com/Game }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }

kill_reward := class(creative_device):

    @editable
    RewardProp : creative_prop = creative_prop{}

    OnBegin<override>()<suspends>:void =
        RewardProp.Hide()

    # Call this from your own elimination handler, passing the result.
    HandleElimination(Result : elimination_result) : void =
        # EliminatingCharacter is ?fort_character — could be empty
        # (environmental death). Chain-unwrap before rewarding.
        if (Eliminator := Result.EliminatingCharacter?):
            # A real killer exists — flash the reward prop into view.
            RewardProp.Show()

Pattern 3 — Failure-chaining a <decides> device call

Option chaining and failable-call chaining share the same failure context. Here we validate a prop and only then read + apply its velocity.

using { /Fortnite.com/Devices }
using { /Fortnite.com }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }

bouncer := class(creative_device):

    @editable
    LaunchButton : button_device = button_device{}

    @editable
    Ball : creative_prop = creative_prop{}

    OnBegin<override>()<suspends>:void =
        LaunchButton.InteractedWithEvent.Subscribe(OnLaunch)

    OnLaunch(Agent : ?agent) : void =
        # Chain BOTH the agent unwrap AND the failable IsValid[]
        # in one guard. Everything after runs only if both succeed.
        if (Player := Agent?, Ball.IsValid[]):
            Ball.ApplyLinearImpulse(vector3{Z := 500.0})

Gotchas

  • ? only works in a failure context. You can't write Player := Agent? as a plain statement. It must live inside if (...), for (...), a <decides> function body, or another failable position. Outside those, the compiler rejects it.
  • ?agent handlers must match the event signature exactly. A listenable(?agent) event calls OnStepped(Agent : ?agent). Don't declare the param as agent — it won't match and won't compile.
  • Failable device calls use [], not (). VaultDoor.IsValid[] and VaultDoor.IsDisposed[] are <decides> — call them with square brackets inside a failure context. Using () gives a wrong-effect error.
  • Don't confuse the empty option with false the boolean. An empty option in Verse is literally the value false, but you don't test it with if (Agent = false) — you unwrap with Agent? and let the branch fail.
  • Comma-chaining is AND, and short-circuits. In if (Player := Agent?, Ball.IsValid[]): if the first clause fails, the second never runs and the block is skipped. Order cheap/likely-to-fail checks first.
  • A field like VaultDoor : creative_prop is never itself an option — it's a concrete reference you set in the editor. Use IsValid[] (a failable check) to confirm it still exists, not ? unwrapping.
  • message params still need localization. Unrelated to options but a common co-occurring trap: if you later pass text to a device method, declare MyText<localizes>(S:string):message = "{S}" — there is no StringToMessage.

Build your own lesson with option_chaining

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →