Tips · EXTRA
Advanced @editable: ToolTip, Categories & Sliders
Use block metadata to give your Verse device's Details panel a near-native feel: grouping, hover tips, and range sliders.
Open the extra →Every checkbox you tick in a Blueprint Details panel — Instance Editable, Pure, Private — is the engine quietly slapping labels on everything behind your back. Verse puts those labels out in the open: one kind is written in angle brackets <> — "badges", hard rules that the Compile button checks one by one, flagging red on any violation; the other kind starts with @ — "sticky notes" that do not change your logic, they just talk to the editor (the Instance Editable eye icon you already know is one of them). This lesson sorts out both label systems, and tears down the no_rollback red error everyone crashes into.
Look back at the notation you have met along the way: the game-start event OnBegin (the Verse version of Event BeginPlay) is followed by <override> and <suspends>, functions that might fail wear <decides>, and a value you want to tune in the editor panel has a line of @editable squatting above it. These "little markers hanging off a name" actually come from two completely different mechanisms.
A specifier (angle-bracket badge) is written in angle brackets <> right after a name or keyword, e.g. <public>, <final>. It sets the hard rules for "how this logic is allowed to be used", enforced by the Compile button (that Compile in the top-left of a Blueprint; in Verse it maps to Ctrl+Shift+B): no inheriting means no inheriting; might-fail means callers must follow the might-fail rules. Think of it as an enchantment affix welded onto your gear — before you take the field, the system inspects every affix, and one failed check lights up red and blocks you.
An attribute (@ sticky note) starts with @, takes a whole line of its own, and sits above something, e.g. @editable. It does not change the logic itself; it tells the UEFN editor or the runtime "please treat this specially" — what @editable does is flip on a variable's Instance Editable eye icon so it shows up in the Details panel, letting designers tune it without touching a single node of logic. It is more like a sticky note taped onto the gear, written for the shopkeeper (the editor) to read.
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
badge_demo_device := class(creative_device):
# attribute: starts with @, takes its own line, sits above the declaration
@editable
var Prize:int = 100
# access specifier: goes right after the identifier
var Secret<private>:int = 0
# effect specifiers: after the parameter list, before the return type
IsLucky()<decides><transacts>:void =
Prize > 50
# override + suspends: two badges you already know
OnBegin<override>()<suspends>:void =
Print("The badge exhibition is open")
Translating the picture: this badge_demo_device is a "badge display case" — four exhibits from top to bottom. The @editable line is a sticky note above the Prize variable (flipping on its Instance Editable eye icon); Secret<private> is a private variable only the class itself may touch; IsLucky()<decides><transacts> is a check that "might fail, and if it fails, can be undone as a whole"; and finally OnBegin is the Verse version of Event BeginPlay — it runs automatically when the game starts and uses Print String to log "The badge exhibition is open". The angle brackets and @ hanging next to each exhibit are the stars of this lesson.
Placement has rules too — memorize four: a class badge sticks right after class (class<final>); a function's effect badges stick after the pair of parentheses, before the colon (F()<decides><transacts>:int); the "who may touch this" access badge sticks after the name (X<private>:int); an @ sticky note always gets its own line, up top. Stick one in the wrong spot — move <public> to the start of the line, or write @editable as an angle-bracket <editable> — and it flags red the moment you press Compile.
Verse badges fall into three groups by what they govern: access badges for "who can see and touch this", effect badges for "what this function will do to the world", and class badges for "what this Blueprint looks like and whether it can be inherited". One table to see them all:
| Category | specifier | In plain words | Example |
|---|---|---|---|
| Access | <public> |
Fully public — anyone can use it | Score<public>:int |
<internal> |
Usable within the same module — the default when you write nothing | Helper<internal>():void |
|
<protected> |
Only this class and its subclasses may touch it | var HP<protected>:int |
|
<private> |
Only this class itself may touch it | var Seed<private>:int |
|
| Effect · exclusive (pick one of four) |
<transacts> |
Everything it does can be rolled back as a whole (implies the read, write, and allocate effects) | AddHit()<transacts>:int |
<varies> |
Same input may produce different results | Mostly seen in official API signatures | |
<computes> |
Pure computation, no side effects: same input, same output | Mostly seen in official API signatures | |
<converges> |
computes plus one more promise: guaranteed to finish | The engine requires it in places like field initializers | |
| Effect · additive (stackable) |
<decides> |
Might fail; call it with square brackets Foo[] |
IsReady()<decides><transacts>:void |
<suspends> |
Async function; may run gradually across multiple simulation ticks | OnBegin<override>()<suspends>:void |
|
| Class / member | <abstract> |
Cannot be instantiated; left for subclasses to complete | base := class<abstract>: |
<final> |
The line ends here: the class cannot be inherited, the member cannot be overridden | boss := class<final>: |
|
<unique> |
Instances have unique identity; comparable with = and usable as map keys | pass := class<unique>: |
|
<concrete> |
Every field has a default value; can be instantiated with the empty archetype {} | @editable custom classes need it | |
<override> |
I am overriding a parent-class member | Tick<override>():void |
Effect badges come with an important team-composition rule: transacts / varies / computes / converges are the "exclusive line" — a function wears exactly one; decides and suspends are the "add-on line" and can stack on top of an exclusive one, e.g. IsPuzzleSolved()<decides><transacts>:void. And when a function wears no effect badge at all, its default is no_rollback — "what is done cannot be undone". That unassuming default will kick up a storm in the next section. Day to day in UEFN, the only ones you will actively write are transacts, decides, and suspends; computes, converges, and varies you will mostly bump into while reading the docs of official ready-made nodes and functions — recognizing them is enough.
Access badges have one more hidden move: they can stick to a variable's name, or separately to the var keyword itself, splitting "read" and "write" permissions in two:
# Anyone can read the score, but only this module can change it
var<internal> Score<public>:int = 0
One last tap on the blackboard: when you write no access badge at all, the default is internal (visible only within the same module/folder), not public. Try to reference something from another module that forgot its <public> and Compile throws back unknown identifier (cannot find that name) — nothing is lost, it just got stopped at the gate.
Earlier lessons said: a function wearing <decides> is called with square brackets [] instead of parentheses. Now we can tell the whole story behind it.
<decides> turns a function into a check that "might not go through" — halfway in, it may declare "no pass" on the spot. Verse treats a "no pass" differently than you might expect: it is not an error, not a crash, but a normal outcome like missing a dice roll — yet it must be caught somewhere. Caught where? In the places that "allow a wire to dead-end" — most typically if (the Branch of Blueprints). But note: a Blueprint Branch asks True/False, while a Verse if asks "can this wire actually go through" — if it cannot, execution turns down the False (else) route. So decides functions may only be called in places like that, and the square brackets Foo[] are the signpost in plain sight: "careful, this step might not go through".
Catching the "no pass" is not enough — someone has to clean up: a check may be halfway done, having already touched a few variables. Verse's approach is hardcore — everything in such a place is speculative execution: it is like test-wiring the whole thing on a "shadow graph" first, and only if every wire goes through does the change land on the real graph; the moment a wire dead-ends, the shadow graph is scrapped and every change is wiped, as if you never wired it. This is exactly why decides always drags transacts on stage with it: transacts is the signed promise "everything I do can be taken back".
In reverse, this also explains that famous red error "This invocation calls a function that has the no_rollback effect, which is not allowed by its context": a function wearing no effect badge defaults to no_rollback; what it does (say, printing a debug line with Print String) cannot be taken back, so Compile naturally bans it from those "might need to undo everything" places. The fix is simple too: move the call outside the Branch and run it first, store the result in a constant, and let the Branch judge only the constant.
The "vault door" below acts the rollback out for you: only one key, but two doors to open. Click "Run next step" and keep your eyes on the value of Keys.
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
vault_device := class(creative_device):
# Only one key
var Keys:int = 1
# Spend the key first, then check: on failure the set is rolled back
TrySpendKey()<decides><transacts>:void =
set Keys -= 1
Keys >= 0
OnBegin<override>()<suspends>:void =
if (TrySpendKey[]):
Print("First door opened, keys left: {Keys}")
if (TrySpendKey[]):
Print("Second door opened")
else:
Print("Door failed to open, Keys rolled back, still {Keys}")
Click "Run next step" to watch the code execute line by line.
Notice that set Keys -= 1 (line 11) ran twice: the same Set node, committed the first time, undone the second — its fate decided by the check right behind it. This "act first, repent later" machinery is, at heart, transactions (one batch of work either all lands or all unwinds) baked into the language; to dig one layer deeper, the extra page "The Effect System and Transactional Rollback: no_rollback Decoded" at the end of this lesson is waiting for you.
With the badges Compile enforces covered, it is the editor's sticky notes' turn. @editable is the note you will use most: write it on the line above one of a device's (creative_device) variables and that variable pops up in the UEFN editor's Details panel — exactly what the Instance Editable eye icon does in Blueprints; designers and level builders can drag a slider to tune your values without touching a single node of logic.
Three things to remember when using it: one, the variable must have a default value; two, its type must be on the supported list (basic types, device references, agent (that "player pin"), arrays, and custom classes marked <concrete>); three, for hover explanations (ToolTip), panel grouping, and range sliders, the @editable family has more advanced forms — see the extra page at the end.
One last high-frequency misconception to clear up: persistable is an angle-bracket badge (specifier), not an @ sticky note! To make a class save-able you write class<final><persistable>; write @persistable and Compile shakes its head on the spot. Telling the two systems apart takes one yardstick: a hard rule Compile must enforce uses a <> badge; a quiet word for the editor / runtime uses an @ sticky note.
Hands-on time: the device below has three keywords carved out — one sticky note, one access badge, and decides' golden partner. Fill them back in, then click "Check answers".
using { /Fortnite.com/Devices }
drill_device := class(creative_device):
# Sticky note: hang the prize field in the editor's Details panel
@____
var Prize:int = 100
# A shield only this class and its subclasses may touch
var Shield<____>:int = 50
# A failable function: who is decides' golden partner?
BossAlive()<decides><____>:void =
Shield > 0
Badge-related Compile reds are probably front-row regulars on the all-Verse error leaderboard. Scout them out in advance so the real thing does not rattle you:
OnBegin<override>()<suspends>:void; missing <override> reports "member already defined in the parent class", missing <suspends> reports a signature mismatch. OnBegin is the Verse version of Event BeginPlay — overriding it is the same as overriding it in a Blueprint.[], a plain function with parentheses (); swap them either way and it flags red. Bracket shape must match "can this step dead-end".spawn{ Fn() }.class<final><persistable>, and a save-able class may hold only constants, never var.Nearly every marker in this lesson is a checkbox or a dropdown in Blueprint. Line them up side by side and the differences jump out.
| In Blueprint | In Verse | Difference |
|---|---|---|
| Tick Instance Editable (the little eye) / Expose on Spawn in a variable's Details panel | @editable, on its own line above the variable |
Same effect: the variable surfaces in the Details panel for designers to tune. But @editable is picky — the variable needs a default value, the type must be on the supported list, and a custom class must carry <concrete> |
| The Private / Protected / Public access setting on a variable or function | <private> / <protected> / <public> |
Same three words, different defaults: Blueprint variables default to Public, while writing nothing in Verse means internal (visible inside the module only) — the culprit behind cross-module unknown identifier errors |
| Tick BlueprintPure on a function (execution pins vanish, the node turns green) | <computes> |
Pure only means "no execution pins"; the engine will not stop you doing damage inside. <computes> is a Compile-enforced promise — same input, same output — and touching state inside flags red |
| Latent nodes (the little clock in the corner: Delay, Retriggerable Delay) | <suspends> |
Blueprint latents only live in the event graph and cannot go inside a function; Verse turns it into a badge you can hang on any function — at the cost of the caller also being <suspends>, or branching off with spawn{} |
| A Const pin / simply never wiring a Set node to a variable | Omitting var — Verse is immutable by default |
Blueprint is mutable by default and locking down takes extra setup; Verse is the reverse — locked by default, and mutability takes an explicit var. The defaults are swapped |
| Tick Abstract in Class Settings / mark a function as not overridable | class<abstract> / <final> |
The switch moved from a settings panel to the name; the meaning is identical. The only gain is that in Verse this information shares a screen with the code, no window-hopping to confirm |
| The Cast Failed pin on a Cast To node / the False pin on a Branch | <decides> plus square-bracket calls, Foo[] |
Blueprint lets you leave the failure pin unwired at your own risk; a Verse <decides> must sit somewhere that catches failure, and on failure every change rolls back — Blueprint has nothing like it, where Sets made before a failed Cast simply stay |
| A SaveGame object / ticking SaveGame on a variable | class<final><persistable> |
Both say "this data survives past the session". Verse's constraints are harder: a persistable class may hold constants only, never var — and it is an angle-bracket badge, so writing @persistable flags red |
| Tooltip, Category and Slider Range in a variable's Details | Block metadata on @editable (see this lesson's extras) |
Blueprint gives you a few text boxes; Verse wants a small block of metadata written by hand. Coverage is not exactly equivalent and shifts between releases — after writing it, eyeball the result in the editor |
The one row to take away from this table is "the defaults are swapped". Blueprint's defaults are permissive: variables are Public, values are mutable, and an unwired Cast Failed pin still compiles. Verse's defaults are tight: internal, immutable, and failure must be caught. The reason is not hard to see — Blueprint was designed for a wire-as-you-go workflow where getting it running matters most; Verse assumes your code will be read by others, edited by others, and referenced across modules, so it reads everything you did not say out loud in the most conservative direction. Early on, half your Compile reds come from exactly this gap.
The other half come from the rollback machinery behind <decides>, which has no Blueprint counterpart at all. A Blueprint Cast Failed merely takes a different execution wire; whatever variables you already changed stay changed. A Verse failure context tears the whole batch of changes up, as though nothing happened. That extra guarantee is the source of the cryptic no_rollback red: an operation that cannot be undone (a Print String, say) is not allowed into a place that may change its mind.
Before the badge exhibition closes, clear three challenge gates to bank your loot. Wrong answers cost nothing — retry as often as you like.
A function has no access badge written at all — what is its default access level?
Why must a <decides> function be called with square brackets, and only inside a place that "might not go through" (like a Branch)?
You want a class to be persistable (player data saved across sessions) — which of these is correct?
Tips · EXTRA
Use block metadata to give your Verse device's Details panel a near-native feel: grouping, hover tips, and range sliders.
Open the extra →Deep Dive · EXTRA
That cryptic error taken apart atom by atom: speculative execution, transactional memory, and why Print cannot enter a failure context.
Open the extra →Bonus · EXTRA
Leaderboards, currency, progression — build your first save system with persistable + weak_map.
Open the extra →