Back to Storyomi
Storyomi Creator Docs

OmiScript 101

The scripting language behind every Storyomi story.

01 — Start Here Full example

A complete OmiScript chapter. It shows backgrounds, character dialogue, choices, variables, conditionals, jumps, and endings all working together.

Register before you reference. The engine validates your script before running it. Characters, backgrounds, variables, and chat IDs must all be set up in Creator Studio first — then you reference them by name in your script. This prevents runtime errors. The example below uses stranger, city_night, and $met_stranger — you'd create each of those in Studio before this script will pass validation.

chapter_one.omi
## The Message// Register 'stranger' in Studio → Characters, 'city_night' in Assets → Backgrounds,// and '$met_stranger' (boolean) in Studio → Variables before running this script. @bg city_night // Narration — no speaker, shown as prose overlay"It was 2 AM when the phone buzzed." // Dialogue — speaker must be registered in Studio → Charactersplayer: "Who texts at this hour?"stranger: "Turn around." // Choice gate — player picks one branch; engine waits here? "What do you do?"  - "Turn around slowly"    player: "My hands are shaking."    stranger: "Smart choice."    // Session variable assignment — persisted with save slots    $met_stranger = true    -> The Reveal   - "Ignore it"    player: "Probably spam."    "She put the phone down. Big mistake."    -> Bad End ## The Reveal@bg alley_night "The figure stepped into the light." // Conditional — branches on a session variable value? if $met_stranger:  stranger: "I knew you'd come."? else:  stranger: "You shouldn't be here." stranger: "We need to talk. Not here." // @end — chapter complete, engine loads the next published chapter@end ## Bad End@bg city_night "Three hours later, she was gone." // @fin ends the entire story, not just the chapter@fin
What this uses:ScenesBackgroundsNarrationDialogueChoicesVariablesConditionalsJumpsEnd
02 — Syntax at a Glance

Every construct in OmiScript is introduced by a single character. Learn the sigils and you know the whole language.

Sigil What it does Example
$varName = exprAssign a session variable — saved with save slots, used for all story branching$score = 0
$varName += exprAdd to a session variable (also -=, *=, /=)$score += 10
$$varName = exprAssign a persistent variable — survives across all playthroughs, never rolled back$$saw_ending_b = true
$$varName += exprAdd to a persistent variable (also -=, *=, /=)$$total_score += 5
## Scene NameStarts a new named scene — used as a jump target## The Rooftop
"text"Narration — no speaker, prose overlay"The rain fell harder."
speaker: "text"Character dialoguesarah: "I'm scared."
speaker (expression): "text"Dialogue with sprite expressionsarah (worried): "I can't."
? "Label"Choice gate — player picks a branch? "What will you say?"
- "Option"Choice option (2-space indent) - "Run"
-> NameJump to a named scene-> The Roof
@endEnd of chapter — engine loads the next published chapter@end
@finEnd of story — game is completely over@fin
@app appIdSwitch to a registered SimOS app@app chatter
@sprite slot charIdStage a character sprite in a slot (left / center / right)@sprite left sarah
@sprite slot clearRemove a sprite from a slot@sprite left clear
speaker (): "text"Auto-stage default sprite and speaksarah (): "Watch out!"
speaker (expression): "text"Auto-stage a named expression spritesarah (worried): "I can't."
@bg filenameSet background image@bg city_night
@cg filenameSet full-screen CG image@cg monster_reveal
@post postIdDeposit a post into the active feed app (social or premium)@post post_001
by: charIdPost author (inside @post block) by: sarah
text: "..."Post caption text (inside @post block) text: "Morning vibes ☀️"
free: trueMark post as always visible — overrides locked_until (premium feeds) free: true
locked_until: exprCondition that must be true to show post — false = blurred lock overlay (premium feeds) locked_until: player_subscribed_to(vip_feed, creator)
teaser: "..."Text shown on the lock overlay when post is locked teaser: "Subscribe to see more 🔒"
? comment on app post:Comment gate — player picks a reply to post in a feed? comment on vip_feed post_001:
? subscribe to appId userId:Subscription gate — player subscribes to a specific user within a premium feed app? subscribe to vip_feed creator:
? call from charId:Incoming phone call gate — Answer / Decline branches? call from sarah:
- "hang up"Optional branch: runs if player ends call early via End Call button - "hang up"
@voicemail vmIdDeposit a voicemail into the active phone app@voicemail vm_missed_01
!system cmdEngine side-effect command (set_time, navigate_to, etc.) — see full list in docs!system set_time 23:45
// commentStripped at compile time// reminder: fix this

@app is for Simulated OS games only. For Standard VN games the engine defaults to the VN context automatically — you never need to write @app unless you are switching between registered SimOS apps (like @app chatter).

03 — Copy-Paste Patterns

The most common structures, shown with realistic IDs. Swap in the character names, asset filenames, and variable names you've registered in your project.

H
Variables & Assignment
variables.omi
## The Score@bg classroom // Variables are declared in Studio → Variables before use.// $var  — session variable, saved with save slots, reset on new playthrough.// $$var — persistent variable, survives all playthroughs — for global flags, meta-unlocks. // Assign initial values at scene start$score = 0$player_name = "Alex" teacher: "Welcome, class. Let's begin." ? "First question: what is 2 + 2?"  - "Four"    teacher: "Correct!"    // Arithmetic operators: +=  -=  *=  /=    $score += 10    -> Next Question  - "Five"    teacher: "...close enough."    $score += 3    -> Next Question  - "I don't know"    teacher: "At least you're honest."    -> Next Question ## Next Question? "Capital of France?"  - "Paris"    $score += 10    -> Results  - "London"    $score -= 5    -> Results ## Results// Chain conditions with ? if / ? elif / ? else? if $score >= 15:  teacher: "Impressive result."  // Persistent flag — this player has scored high at least once, ever  $$high_scorer = true? elif $score >= 5:  teacher: "Not bad."? else:  teacher: "We'll keep practicing." @end
A
Branching Choice
branching_choice.omi
// Choices can be conditional — an option only appears when its [$condition] is true.// Conditions read session variables ($var) declared in Studio → Variables. ? "What do you say?"  - "I believe you" [$trust >= 1]    // This option only shows if $trust is at least 1    player: "I believe you."    sarah: "Thank you. Really."    // String assignment — use this value in conditionals later    $route = "friendship"    -> Good Path   - "I don't trust you"    // No condition — always visible    player: "I don't trust you."    sarah: "That's fair. But you're wrong."    $route = "tense"    -> Tense Path   - "Stay quiet" [$met_sarah == false]    // Only available if player hasn't met sarah yet    "You said nothing. Safer that way."    -> Quiet Path
B
Simulated OS Messenger Chat
messenger_scene.omi
// Switch to a messenger-type app registered in Studio → Apps.// All @chat content is deposited into that chat's message log.@app chatter // Open a specific chat — chatId must be registered under this app in Studio.@chat chat_sarah  // Messages drip in one at a time as the engine runs  sarah: "hey, you there?"  sarah: "hello??"   // Reply gate — player picks one option to send as their message  ? reply:    - "sorry, was asleep"      player: "sorry just woke up lol"      sarah: "okay good, we need to talk"      // Track that the player replied — useful for branching later      $replied_sarah = true      -> Replied     - "leave on read"      // No player message — sarah gets no response      -> Left On Read ## Repliedsarah: "meet me at the usual spot. 20 mins."@end ## Left On Read// Push a notification into the player's notification shade@notify chatter "Sarah" "is waiting for your reply..."@end
C
Conditional Branching
conditional.omi
// ? if / ? elif / ? else — conditional blocks.// Evaluates against session ($var) or persistent ($$var) variables.// Only the first matching branch executes. ? if $route == "friendship":  sarah: "I'm glad we talked."  player: "Me too."? elif $route == "tense":  sarah: "I hope you change your mind."  player: "Maybe."? else:  // Fallback — runs when no other branch matched  "They stood in silence." // Comparison operators: ==  !=  >  <  >=  <=// Logical: and  or  not// Example multi-condition: ? if $score >= 10 and $met_sarah:
D
Phone Call (SimOS)
phone_call.omi
## The Call// Switch to a phone-type app registered in Studio → Apps@app phone // Incoming call gate — player must pick Answer, Decline, or another branch.// Dialogue inside a branch becomes in-call conversation in the Phone app.? call from sarah:  - "answer"    sarah: "Hey. Are you alone?"    player: "Yeah, why?"    sarah: "I need you to listen carefully."    sarah: "Don't come to the office tomorrow."    -> After Call   - "decline"    // No in-call dialogue — goes straight to the branch body    -> Missed Call   // 'hang up' is a special branch — runs when player taps End Call mid-conversation  - "hang up"    sarah: "...hello?"    -> Hung Up ## After Call"She hung up before you could ask anything."@end ## Missed Call// Voicemail — deposited into the phone app's voicemail inbox@voicemail vm_sarah_001  from: sarah  duration: 0:12  transcript: "It's me. Call me back. Please."@end ## Hung Up"You stared at the phone. Had that been a mistake?"@end
E
Social Feed (SimOS)
social_feed.omi
## The Post// Switch to a social_feed-type app registered in Studio → Apps@app social_feed // Posts deposit into the feed as the engine runs — displayed newest-first in the UI.// 'image' is a logical name from Studio → Assets → CGs (no file extension).@post post_morning  by: sarah  text: "Another late night at the office. Someone send help ☕"  image: sarah_office  likes: 12  // likeable: true — player can tap the like button  likeable: true // Comment gate — player picks a reply to a specific post.// appId and postId must reference what's already been deposited above.? comment on social_feed post_morning:  - "Hang in there!"    // Player's comment is posted to the thread    player: "Hang in there, you've got this!"    sarah: "Aw, thanks 🥺"    -> After Comment   - "Say nothing"    // No comment posted — thread stays empty    -> After Comment ## After Comment// Push a banner notification into the player's notification shade@notify social_feed "Sarah" "liked your comment""Your notification lit up a moment later."@end
F
Premium Feed with Subscription Gate (SimOS)
premium_feed.omi
## The Exclusive// Premium feed — requires a premium_feed-type app in Studio → Apps@app vip_feed // free: true — always visible regardless of subscription status@post post_free_001  by: creator  text: "Hey everyone, big news coming soon 👀"  likes: 204  likeable: true  free: true // locked_until — post is blurred with a lock overlay until the condition is true.// player_subscribed_to(appId, userId) is a built-in engine expression.// 'image' is a logical name from Studio → Assets (no file extension).@post post_locked_001  by: creator  text: "Here's the full behind-the-scenes video from last night."  image: bts_video_thumb  locked_until: player_subscribed_to(vip_feed, creator)  teaser: "Subscribe to unlock exclusive content 🔒" // ? subscribe to — narrative-driven subscription gate.// Use this when subscription should happen at a specific story beat// rather than passively via the feed header button.? subscribe to vip_feed creator:  - "Subscribe"    creator: "Thank you so much! 🥺"    // Persistent — survives across playthroughs, so future chapters know    $$subscribed_vip = true    -> Subscribed  - "Maybe later"    -> Skipped ## Subscribed"The locked post shimmered and revealed itself."@end ## Skipped"The blurred post remained. For now."@end
G
Character Sprites
sprites.omi
## The Confrontation// 'rooftop_night' is a logical name from Studio → Assets → Backgrounds@bg rooftop_night // Auto-stage: speaking with () triggers automatic sprite placement.// First speaker goes center; second speaker pushes first to the side.sarah (): "You followed me up here."player (): "You left me no choice." // Named expression — must be registered in the character's sprite map in Studio.// Use () for the default expression, (name) for a registered variant.sarah (angry): "Don't make this harder than it has to be." // Explicit placement — overrides auto-staging, puts marcus in the left slot.// Useful when you need precise control over positioning.@sprite left marcus marcus: "Nobody's going anywhere." // Clear a slot — removes the sprite visually when a character exits.@sprite left clear // sarah and player remain staged in their auto-assigned slots.sarah: "It's just us now." // @end — chapter complete. Use @fin to end the entire story.@end
04 — Character Sprites

Standard VN games support character sprites — portrait images that appear over the background while characters speak. Upload sprite images in Assets → Sprites, then link them to characters in the Characters panel under each character's sprite map.

Auto-staging — when a character speaks with sarah (): "text", the engine automatically places them in the best available slot. The first speaker goes center; when a second character speaks, the first moves to the side and the new character takes the other side.

Persistence — sprites stay on screen until you explicitly clear them with @sprite left clear. Scene headers (=== Scene: Name ===) do not clear sprites automatically.

Depth — the character who spoke most recently always floats to the top. Non-speaking characters dim slightly to keep focus on the active speaker.

Expressions — register multiple images per character (e.g. default, happy, angry) in the Characters panel, then reference them with sarah (angry): "text". Falls back to default if the expression isn't found.

Syntax What it does
sarah (): "text" Auto-stage sarah's default sprite and speak
sarah (angry): "text" Auto-stage sarah's angry expression sprite
@sprite left sarah Explicitly place sarah in the left slot (overrides auto-staging)
@sprite center clear Remove whoever is in the center slot
sarah: "text" Dialogue with no sprite — if sarah was already staged, she stays visible but no new staging occurs

Sprites only appear in the Standard VN template. SimOS games (the phone template) do not render sprites.

05 — Referencing Your Assets

When you upload an asset in Creator Studio, it is assigned a logical name — a short identifier without a file extension. Use that logical name directly in your script.

Asset type Where to upload How to use in script
Background imageStudio → Assets → Backgrounds@bg your_logical_name
CG imageStudio → Assets → CGs@cg your_logical_name
Character spriteStudio → Assets → Sprites, then link in Characterssarah (): "text" or @sprite left sarah
NPC characterStudio → Characterssarah: "Hey!"
VariableStudio → Variables$varName = value or $$varName = value
Messenger chatStudio → Apps → Chats@chat chat_sarah

Logical names are set when uploading an asset (defaults to filename without extension). They can be renamed in the Studio asset browser and must be unique per game. For example, a file called city_night.jpg gets the logical name city_night — write @bg city_night in your script, without any extension.

06 — Variables & Assignment

Variables let you track player choices, scores, flags, and any state that shapes the story. Declare them in Studio → Variables first, then read and write them anywhere in your script.

Single dollar sign $var — a save-slot variable. Stored in the player's save and restored whenever they load it. Use $var for almost everything story-related: branching flags, relationship scores, counters, player choices — anything that should feel different on a fresh run but persist across save/load within a playthrough.

Double dollar sign $$var — a persistent variable. Stored in the player's global state, completely separate from save slots. It is never rolled back — even starting a new game leaves it intact. Use $$var for meta-game state that outlives any single playthrough: "has this player ever reached Ending B", global achievement flags, or cross-playthrough unlocks.

Syntax Operator Effect
$score = 0= Set to a literal value or expression
$score += 10+= Add (numbers only)
$score -= 5-= Subtract (numbers only)
$score *= 2*= Multiply (numbers only)
$score /= 2/= Divide (numbers only)

Reading in conditions — use ? if $var == value: to branch on a variable. Supports ==, !=, >=, <=, >, <.

Interpolation — write "Hello $player_name" inside any dialogue or narration string and the engine substitutes the current value at runtime.

Gated choice options — add [$var >= 1] after an option label to hide it unless the condition is met.

The linter warns if you use a numeric operator (-=, *=, /=) on a variable declared as type string, or if you assign a literal value that doesn’t match the declared type — e.g. assigning "hello" to a number variable. These are warnings, not errors — the script will still compile.

07 — What Doesn't Work Yet Alpha

OmiScript is under active development. The engine runs correctly for everything on this page. The features below are either partially implemented or reserved for a future release — don't waste time trying to build them in alpha.

Not yet available in alpha
  • Audio (!system play_music / stop_music)

    Music and sound effect commands are parsed and compiled but the audio engine is not yet wired — they will be silently ignored at runtime.

  • Persistent variable reads across chapters ($$var)

    Persistent variables can be declared in Studio and assigned with $$varName = value. Assignment is compiled and stored, but reading $$var values in conditions across separate play sessions is not yet wired end-to-end.

  • Nested choices

    Choices inside choice branches may cause unexpected engine behaviour. Keep branching flat for now.

Found something broken that isn't listed here? Report it in Discord or send us an email — we read everything.

Ready to start writing?

Create a free account