Extensions
Dice+ header
Dice+ icon
Dice+

Missing Link Dev

Dice+ Banner

Dice+


Support Development

If you enjoy Dice+ and my other extensions, please consider supporting development on Patreon! Your support helps me continue creating and maintaining quality extensions for the Owlbear Rodeo community.


A feature-rich 3D dice roller extension for Owlbear Rodeo with deterministic physics, customizable dice, and advanced dice rolling capabilities. Roll your dice in beautiful 3D with realistic physics simulation while maintaining complete control over your dice collection and rolling options.

Table of Contents

Features

  • 3D Physics-Based Dice Rolling: Realistic dice rolling with deterministic physics using Three.js and Rapier physics engine
  • Customizable Dice: Create custom dice with unique colors, materials, sizes, and shapes
  • Quick Roll Interface: Fast dice selection with intuitive left/right click controls
  • Keyboard Roll Tool: Optional keyboard rolling with customizable dice and notation shortcuts, activated with R
  • Advanced Dice Notation: Supports complex dice notation including advantage/disadvantage, exploding dice, re-rolls, min/max values, and more
  • Dice Bag Management: Organize and manage your dice collection with filters, active/jailed states, and custom configurations
  • Material System: Choose from Standard and Galaxy materials with extensive customization options
  • Statistics Tracking: View detailed roll history and statistics for your dice
  • Developer Integration: Broadcast channels for sending roll requests and receiving results from other extensions

Adding Dice

Add dice using quick roll buttons, the notation input, or the optional keyboard roll tool:

Quick Roll Buttons

The quick roll interface provides fast, visual dice selection with modifier controls:

  • Add/Subtract Dice

    • Left click on a dice button to add one die of that type
    • Right click on a dice button to subtract one die
  • Advantage/Disadvantage

    • Left click the A/D button to add advantage (roll an extra die and keep the highest)
    • Right click the A/D button to add disadvantage (roll an extra die and keep the lowest)
    • Visual badges show the number of advantage or disadvantage dice applied
  • Modifiers

    • Use the + and - buttons to adjust the modifier value (adds or subtracts from the final total)
    • Modifiers range from -99 to +99
  • Exploding Dice

    • Left click the flame icon to enable exploding dice (when a die rolls its maximum value, roll it again and add the result)
    • Right click the flame icon to remove the exploding modifier
    • Badge shows the maximum number of explosions allowed (up to 10)

Dice Notation Input

For more complex rolls, use the dice notation input field. This supports the full range of dice notation features (see Dice Notation Reference below).

Examples:

  • 2d20kh1+5 - Roll 2d20, keep highest, add 5
  • 4d6dl1 # Ability Score - Roll 4d6, drop lowest, with label
  • 3d6{Fear} # Fire + 2d6{Hope} # Ice - Use specific dice models with descriptions
  • (2d6, 1d12)kh1 - Roll both, keep the higher total (see Grouped Rolls)
  • 1d12, 2d6, 2d20kh1 - Roll all at once as independent results (see Multiple Rolls at Once)
  • 6#4d6dl1 - Repeat the same roll six times (see Repeating a Roll)

Roll History Panel

Below the notation input, you'll find the Roll History Panel which automatically tracks your recent rolls:

  • Quick Reroll: Click any notation in the history to instantly populate the input field
  • Pin Favorites: Click the pin icon next to any notation to pin it
    • Pinned rolls stay at the top of the list and won't be auto-cleared
    • Maximum of 10 pinned notations
  • Recent Rolls: Shows your last 20 unpinned rolls
  • Toggle Help: Click the help icon (?) to switch between viewing your roll history and the notation help text
  • Cloud Sync (Premium): Your dice bag, roll history, notation history, hotbars, and stats sync across devices. Each linked Owlbear Rodeo account on your premium subscription gets its own independent cloud-backed dataset — linking a second OBR account does not merge bags, and edits made on one linked account do not affect the others.

Tips:

  • Pin your commonly used rolls (damage, attacks, ability checks) for instant access
  • The history persists between sessions via local storage

Dice Bag

The Dice Bag is your dice collection management system where you can store, organize, and customize your dice.

Filters

Filter your dice collection by type and state:

Type Filters:

  • Filter chips show D4, D6, D8, D10/D100, D12, and D20
  • Click a filter chip to show only that dice type
  • Click again to remove the filter and show all dice
  • Multiple type filters can be active simultaneously

State Filter:

  • Filter by dice state: All, Active, Available, or Jailed
  • All - Shows all dice regardless of state (default)
  • Active - Shows only dice in your active rotation
  • Available - Shows only dice that can be called by name
  • Jailed - Shows only dice that are excluded from rolls
  • Use state filters to quickly manage specific groups of dice

Dice States: Active, Available, and Jailed

Dice in your bag can be in one of three states, giving you flexible control over your dice collection:

  • Active Dice: These dice are in your active rotation

    • Quick roll buttons automatically cycle through active dice as you roll
    • Tip: For Daggerheart, create two d12s (Hope/Fear) and only have those two active. Every roll will alternate between them, making it easy to track your Fear and Hope scores.
  • Available Dice: These dice are stored but can be called by name

    • Not included in the automatic rotation for quick roll buttons
    • Can be summoned by name using dice notation: 2d6{Fireball} will use the die named "Fireball"
    • Perfect for situational dice (special attacks, spells, conditional abilities)
    • Will be used if no active dice and die type is used in notation
    • Tip: Keep character-specific dice or special effect dice as "Available" so they don't clutter your quick rolls but are ready when you need them by name
  • Jailed Dice: Completely excluded from all rolls

    • Not available for quick rolls or dice notation
    • Useful for dice that aren't rolling well or for storing alternate character sets
    • Tip: Create dice sets for different characters and jail/activate them to quickly switch between character loadouts

Rolling Logic:

When you roll dice (via quick buttons or notation):

  1. Active dice are used first - The system cycles through your active dice in order
  2. If no active dice exist, the system checks for available dice and uses those
  3. If no active or available dice exist, the roll uses instant calculation (math.random) instead of physics simulation

Quick Roll Button Visibility:

Quick roll buttons only appear when you have at least one active die of that type. If all your d6s are "Available" or "Jailed", the d6 quick roll button will be hidden. This keeps your UI clean and only shows the dice you want in your active rotation.

The dice bag enforces a maximum capacity of 50 total dice (active + available + jailed combined). Unlimited Dice capacity for Premium users (see Patreon for upgrade)

Creating and Editing Dice

Click the "Add Dice" button to create new dice with custom properties:

Dice Type Selection

Choose the dice type Set (creates all dice type with same settings), D4, D6, D8, D10, D12, D20.

Name

Give your dice a custom name to easily identify them in your collection.

Preview

Real-time 3D preview of your dice with the selected shape, material, and settings.

Shape Selection

Choose the physical shape for your die:

  • Each dice type supports multiple geometric shapes (e.g., D6 can be a cube or alternative shapes)
  • The shape affects the visual appearance and rolling physics

Size Selection

Adjust the dice size from 25% to 300% of the standard size

Material Selection

Choose from multiple material types with extensive customization options. Each material provides unique visual effects and properties:

Standard Material - A physically-based material with realistic lighting and surface properties:

  • Color: Base color of the dice body
  • Font Color: Color of the numbers on the dice
  • Number Depth (0.0-1.0): How deeply the numbers are engraved into the surface
  • Roughness (0.0-1.0): Surface roughness (0 = mirror-like, 1 = matte)
  • Metalness (0.0-1.0): Metallic appearance (0 = non-metallic, 1 = fully metallic)
  • Sheen (0.0-1.0): Soft sheen/glow effect on the surface

Galaxy Material (Premium Only) - A stunning cosmic material with animated star fields:

  • Font Color: Color of the numbers
  • Number Depth (0.0-1.0): How deeply the numbers are engraved
  • Primary Color: Primary color of the galaxy nebula
  • Secondary Color: Secondary color blended into the galaxy
  • Star Density (0.5-1.5): How many stars appear in the galaxy effect
  • Brightness (0.0-2.0): Overall brightness of the galaxy effect

More material types coming soon!

Dice Stats and Log

View detailed statistics and history for your dice:

  • Roll history showing all past rolls
  • Statistical analysis of roll outcomes
  • Distribution charts showing roll frequencies
  • Individual die performance tracking

Sharing Dice

Share your custom dice with other players in your game session. There are two ways to share dice:

Direct Sharing within Owlbear Rodeo

Send dice directly to other connected players:

  1. Open the Dice Bag (both sender and receiver must have their dice bags open)
  2. Click the three-dot menu on any dice card you want to share
  3. Select "Share" from the menu
  4. Choose a recipient from the list of connected players, or select "Copy to Clipboard" for manual sharing

Important Notes:

  • The receiving player must have their dice bag open to receive the shared dice
  • Premium materials require premium subscription - If you try to share dice with a premium material to a non-premium user, they will not be able to accept it
  • Dice bag capacity limits apply - Free users can store up to 50 dice. If the receiver's dice bag is full, they cannot accept new dice

Copy to Clipboard Sharing

Share dice settings as JSON that can be pasted:

  1. Click the three-dot menu on any dice card
  2. Select "Share" → "Copy to Clipboard"
  3. A dialog will appear with the JSON settings - manually select and copy the text (Ctrl+C or Cmd+C)
  4. Share the JSON with other players (Discord, chat, etc.)

To receive dice from clipboard:

  1. Open your Dice Bag
  2. Click the Paste icon (clipboard icon) in the header next to the close button
  3. Paste the JSON into the dialog and click "Confirm"
  4. The dice will be validated and added to your bag if compatible

Validation:

  • Premium materials can only be received by premium users
  • Dice bag must not be at capacity (50 dice limit for free users)
  • JSON must contain valid dice settings

Sharing dice is perfect for:

  • Standardizing dice across your party
  • Sharing custom themed dice sets
  • Distributing character-specific dice (Fear/Hope, special abilities, etc.)
  • Helping new players get started with pre-configured dice

Custom Font Packs (Premium)

Upload your own dice number atlases. Build a font in the in-app Number Generator, export it as a font pack .zip, then upload it from any dice's Font Type picker. The pack becomes available on every die you create or edit, and other players in the room see your custom font on dice you roll.

Pack format

A font pack is a .zip containing three 1024×1024 PNGs:

  • numbers<suffix>.png — main number atlas (used by every die except d100 tens and Fate/Fudge)
  • numbers10x<suffix>.png — d100 tens digits
  • numbersFudge<suffix>.png — Fate/Fudge symbols

The optional <suffix> matches the Filename suffix field in the Number Generator (e.g. _norse); leave it blank for plain numbers.png/numbers10x.png/numbersFudge.png. The Number Generator's Download All (ZIP) button produces this layout automatically.

Limits

  • Premium account required
  • 1 MB per zip
  • 50 packs per user

Managing packs

Packs you uploaded appear under the Custom group inside the Font Type dropdown. Use Manage fonts… in the same dropdown to delete a pack — dice that referenced it revert to Standard.

When you share a die that uses a custom font, the recipient sees the die with the Standard font instead, since custom fonts are private to the uploader.

App Settings

Access App Settings from the gear icon in the dice bag header. Settings are saved automatically to local storage and persist between sessions.

Keyboard Roll Tool

Enable App Settings → Keyboard rolling → Enable hotkey tool, then Save. This setting is off by default. Once enabled, press R or click the Dice+ tool in the OBR toolbar to start a keyboard roll.

The action panel opens in notation mode with pins/history collapsed, preserving your normal layout preference. Focus moves into the panel, not the notation input, so assigned dice shortcuts are handled there instead of activating OBR tools. Click the notation field whenever you want to type or edit ordinary dice notation; shortcut expansion pauses while you edit.

Default hotkeys (case-insensitive):

Label Notation Hotkey
D4 d4 A
D6 d6 S
D8 d8 D
D10 d10 F
D12 d12 G
D20 d20 H
D100 d100 J
Keep highest kh1 K
Drop highest dh1 L
Minimum min M
Maximum max X
Reroll r Q

Building a roll:

  • Type 3a+5 or aaa+5 to enter 3d4+5.
  • A matching dice shortcut increments the trailing dice term, keeping its modifiers. For example, hkkh produces 2d20kh2: the final H increases the d20 count even after kh2.
  • A different dice shortcut adds another term: as produces 1d4+1d6. An explicit operator starts a new term, so h+2h produces 1d20+2d20.
  • Repeated K or L presses increase the keep/drop count. The opposite key reduces it, removes it at zero, then switches direction. For example, hhkkl produces 2d20kh1.
  • Type numbers and operators directly, including +, -, *, /, parentheses, and comparisons. Explode uses the literal !: a!>3 produces 1d4!>3. Follow min/max or reroll shortcuts with parameters as needed.
  • Backspace deletes one character from the expanded notation. Hold-to-repeat is ignored; tap a dice key for each additional die.

Finishing a roll: Press Enter to roll to the audience shown on the roll button. The panel waits until the roll has been sent before closing and restoring the previous tool. Invalid notation or a send failure stays available for correction or retry. Press Escape to cancel before sending; closing the panel or choosing another tool also ends an unsubmitted roll. Once sending starts, it is allowed to finish.

Customizing hotkeys: Expand Hotkeys below the toggle to edit bindings. The compact list labels built-ins by name and custom entries as Custom1, Custom2, and so on. Choose Add custom hotkey, enter the notation fragment to insert and its letter, then Save. Custom fragments insert literally. Clear a default key to free its letter, remove custom rows with their × button, or use Reset to restore the defaults. Duplicate letters, R, numbers, operators, and editing controls cannot be assigned. These settings are saved in your browser.

Popover Layout

Sets how the dice popover that opens from the toolbar is laid out. Pick an option using the layout previews.

Option Description
Vertical Tall column of dice — options expand to the side of each die (default)
Horizontal Wide row of dice — options expand below each die
Input Only Just the dice notation field, with no quick dice buttons
Hotbar Your saved rolls, one tap each

Horizontal is the same layout turned a quarter turn and read the other way around: the dice bag leads the row, the roll button follows it, and the dice run left to right ending with the d20. Each die's advantage/modifier/explosion controls drop down beneath it, and the roll button's label reads top to bottom.

Text input mode returns to the portrait window in every layout, with the roll button below the notation field.

Input Only stays on the notation field permanently — there are no dice buttons to switch back to. The clear button empties the field instead of leaving notation mode, and the roll button is held disabled until the field holds a valid notation.

Hotbar

The Hotbar layout replaces the quick dice buttons with your own saved rolls. If you roll the same handful of things all session — an attack, its damage, a saving throw — each one becomes a single button.

The row reads left to right: the dice bag, the roll target and the notation toggle, then a divider, then your hotkeys, with the panel carousel at the far end. Everything left of the divider is the app; everything right of it is your own rolls.

Creating a hotkey. Press the + in the row. A window opens over the map with three fields:

  • Name — shown in the hotkey's tooltip alongside its notation. "Attack", "Stealth", "Fireball".
  • Dice Notation — any notation the notation field accepts, validated as you type. Save stays disabled until it is valid.
  • Icon — one of the dice icons, picked from a grid. This is what the button shows, so pick one you will recognise at a glance.
  • Colour — a swatch to tint the icon with. Colour is the fastest way to tell a row of hotkeys apart at a glance: red for damage, green for healing, blue for saves. The icon grid draws in the colour you pick, so it doubles as a preview.

Each panel holds up to 10 hotkeys. Right-click a hotkey (or long-press on touch) to open it for editing, where it can also be deleted. Hovering a hotkey shows its name and notation.

Reordering. Drag a hotkey along the row to move it; the others step aside to show where it will land. Works with a mouse or by touch. A tap still rolls — the drag only starts once you have actually moved the hotkey.

Panels. The carousel at the end of the row pages between panels: the arrows step through them, wrapping at either end, and the number between them shows which panel you are on. Click that number to manage panels — add, rename, delete, share, or jump straight to one. Up to 10 panels, and the last one cannot be deleted. One panel per character, or one per combat role.

Sharing a panel. A whole panel can be handed to the other people in your room, so a GM can set up a monster's attacks once and give them to whoever is running it, or a player can pass their character's rolls to someone covering for them.

In the panel manager, press the share icon on a panel and choose who gets it:

  • A player — sent to that one person.
  • Everyone — offered to everyone else in the room.
  • Copy as text — puts the panel on your clipboard as a block of text you can send through chat, a document, or to a room you are not in. The recipient pastes it with Import.

The recipient sees a window with the sender's name and every hotkey in the panel — its icon, colour, name and notation — and chooses Accept or Decline. Nothing is added until they accept, and accepting adds it as a new panel; their existing panels are never overwritten. If a panel by that name is already there, the copy gets a number.

Offers arrive whether or not the recipient has Dice+ open, and queue up if several land at once. A panel cannot be accepted once you are at 10 panels — delete one to make room. Sharing a panel shares the rolls, not the dice: the recipient's own dice bag styling is what they will see when the hotkeys roll.

Roll target. The person icon next to the dice bag sets who sees the rolls: Everyone, Self, GM & Self, or GM Only. It applies to every hotkey in the bar, and is remembered between sessions.

Ad-hoc rolls. The notation toggle next to the roll target opens the usual dice notation field, so anything not worth saving as a hotkey is still one tap away. Clearing it returns to the hotbar.

All of the hotbar's editing happens in a window that floats over the map rather than inside the toolbar popover — the hotbar is a single row tall, so a menu or dialog inside it would be cut off.

Hotkeys are saved to local storage and persist between sessions. Premium users have them backed up to the cloud and synced across devices, alongside roll history and notation history.

Keep Notation Open

Off by default. When enabled, rolling from the dice notation input clears the field but stays on it, ready for the next notation, instead of closing back to the dice buttons.

The Input Only layout always behaves this way, since it has no dice buttons to return to.

Keep Last Modifier

Off by default. Normally a roll clears the dice buttons completely, including the modifier set on each die. When enabled, every die keeps the modifier it was last rolled with, so rolling the same bonus again is a single click.

The memory is per dice type: the d20 remembers its own modifier, the d12 its own, and rolling one never disturbs another. Dice left out of a roll keep whatever they were already holding.

A carried modifier stays hidden until you pick that die up again — a modifier on a die with no count adds nothing to a roll. Roll a die back at zero to forget its modifier, or press Clear to forget them all at once.

Sound

Toggle dice sound effects on or off.

Roll Timestamp

Off by default. When enabled, the roll results panel shows the time of each roll (including seconds) right-aligned on the header row.

Roll Settle Time

Controls how quickly rolled dice come to rest after being thrown. Adjusts the physics of the roll — gravity, damping, and throw force all scale together so dice still cross the tray regardless of the setting.

Option Description
Very Fast Dice slam down and stop almost immediately
Fast Dice settle quickly with minimal bounce
Normal Standard settle time — the default feel
Slow Dice bounce and roll longer before stopping
Very Slow Dice float, bounce, and spin for a long time

Dice Clear Delay

Controls how long your own rolls remain visible on the tray before auto-clearing. Ranges from 1 to 10 seconds. This applies only to your own rolls — other players' rolls clear on their own schedule.

Performance Mode

Controls how other players' dice are rendered on your screen. Does not affect your own dice.

Option Description
Normal Show all dice materials from other players
Default Dice Only Show simplified dice for other players (better performance)
Results Only Skip dice animations for other players entirely — show results only (best performance)

Dice Notation Reference

Dice+ uses a dice notation system compatible with Roll20 and RPG Dice Roller, with some extensions and variations.

Basic Rolls

  • d20 - Roll a single d20
  • 2d6 - Roll two six-sided dice
  • 1d8+5 - Roll a d8 and add 5
  • 3d6-2 - Roll three d6 and subtract 2

Modifiers

Advantage/Disadvantage (Keep/Drop High/Low)

Keep Operators - Keep specified dice, drop the rest:

  • 2d20kh1 - Roll 2d20, keep highest 1 (advantage)
  • 2d20kl1 - Roll 2d20, keep lowest 1 (disadvantage)
  • 4d6kh3 - Roll 4d6, keep highest 3 (common for ability score generation)

Drop Operators - Drop specified dice, keep the rest:

  • 2d20dl1 - Roll 2d20, drop lowest 1 (same as keep highest - advantage)
  • 2d20dh1 - Roll 2d20, drop highest 1 (same as keep lowest - disadvantage)
  • 4d6dl1 - Roll 4d6, drop lowest 1 (keep highest 3)

Tip: Keep/drop can also apply to whole rolls instead of individual dice — (2d6, 1d12)kh1 rolls both and keeps the higher total. See Grouped Rolls.

Chaining Keep/Drop

Keep/drop operators can be chained. They apply in the order written, each step operating only on the dice still kept by the previous step:

  • 4d10dh1kh1 - Drop the highest, then keep the highest of the remaining 3. Rolling [2, 4, 5, 9] drops the 9 first, then keeps the 5.
  • 4d10kh3kl1 - Keep the highest 3, then keep the lowest of those. Rolling [2, 4, 5, 9] keeps [4, 5, 9], then results in 4.
  • 6d6dl2dh1kh2 - Drop the 2 lowest, drop the highest, keep the highest 2 of what's left.

Each step is validated against the dice remaining at that point in the chain:

  • khN/klN needs at least N dice remaining — 2d10kh3 is invalid.
  • dhN/dlN must leave at least one die — 4d10dh4 is invalid, and so is 4d10dh3kh2 (only 1 die remains after dh3).

With exploding dice, position in the chain matters:

  • 2d6!kh1 - Explosions join the pool as separate dice before the keep. Rolling [1, 6] where the 6 explodes into a 2 gives a pool of [1, 6, 2] → kh1 keeps 6.
  • 2d6!!kh1 - Compounding merges explosions into the original die first: [1, 6+2] → kh1 keeps 8.
  • 2d6kh1! - The keep resolves over the original dice only; explosion dice then follow their parent die (kept parent → explosion counts, dropped parent → explosion is dropped).

Note: Re-rolls (r, ro, rh, rl), min/max, and unique (u) always resolve while the dice are rolling, before any keep/drop is applied — their position in the chain doesn't change that. Matching explode (!m) supports a single keep/drop operator and cannot be combined with a chain.

Min/Max

  • 1d20min10 - Result cannot be less than 10
  • 1d20max15 - Result cannot be more than 15

Exploding

Exploding dice re-roll when they hit their maximum value and add the new roll to the total:

Basic Exploding:

  • 1d6! or 1d6e - Explode on 6 (implicit max value)
  • 1d6!6 - Explode on 6 (explicit single value)
  • 1d6!6:3 - Explode on 6, maximum 3 explosions
  • 1d6!>4 - Explode on any value greater than 4

Multiple Values (Comma-Separated):

  • 1d6!1,6 - Explode on 1 or 6
  • 1d6!1,6:2 - Explode on 1 or 6, maximum 2 explosions

Compounding (!!):

Compounding explosions combine all explosion rolls into a single die value instead of adding separate dice:

  • 4d6!! - Explode on 6, sum all explosions into one die (e.g., [4, 6+6+2] → [4, 14!!])
  • 3d6!!kh1 - Compound on 6, keep highest (e.g., [4, 6+6+2, 3] → [4, 14!!, 3] → keep 14!!)

Note: Exploding dice spawn new dice dynamically during the roll, which causes timing-dependent physics behavior that cannot be perfectly synchronized between clients. Remote viewers may see slightly different animations, but the roller's broadcasted results are always the source of truth.

Matching Explode

Matching explode triggers when multiple dice in a group show the same value. Instead of checking each die individually, it checks if N dice match — then rolls one additional die per matching group. If the new die also matches the original value, it chains.

Basic Matching Explode:

  • 2d10!m - If both d10s show the same value, roll 1 extra d10
  • 3d10!m3 - All 3 must match to trigger (match count = 3)
  • 2d10!m:2 - Match explode, max 2 chain explosions

With Selectors (filter which values can match):

  • 2d10!m>5 - Only values greater than 5 can form a match
  • 3d10!m3>=4:2 - 3 dice must match on a value ≥4, max 2 explosions

Multiple Match Groups: If multiple distinct matching groups exist, each group triggers its own explosion. For example, 4d6!m rolling [3, 3, 5, 5] would spawn 2 explosion dice — one for the 3s and one for the 5s.

Chaining: When a matching explosion die lands on the same value that originally matched, it chains and spawns another explosion die (up to the max explosion limit).

With Advantage/Disadvantage: When combined with keep/drop operators (e.g., 3d10kh2!m), explosion dice only count toward the matching group's total. The system evaluates all possible kept-dice combinations and picks the best (advantage) or worst (disadvantage) total. For example, 3d10kh2!m rolling [2, 2, 7] with explosion [8] would compare: keeping {2, 2} + explosion 8 = 12 vs keeping {7, 2} = 9, and choose 12.

Validation: The dice count must be at least the match count. 2d10!m3 is invalid because you can't match 3 dice with only 2.

Note: Matching explode dice spawn dynamically like regular exploding dice, so remote viewers may see slightly different animations.

Re-Roll

Re-roll dice that meet certain conditions:

Basic Re-Roll:

  • 1d20r - Re-roll on 1 (implicit minimum, infinite)
  • 1d20ro - Re-roll once on 1
  • 1d20r1 - Re-roll on 1 (explicit single value)
  • 1d20r<5 - Re-roll on any value less than 5
  • 1d6r6:3 - Re-roll on 6, maximum 3 re-rolls

Multiple Values (Comma-Separated):

  • 1d20r1,2 - Re-roll on 1 or 2
  • 1d8r1,2,7,8 - Re-roll on extreme values

Note: Re-roll modifiers replace dice dynamically during the roll, which causes timing-dependent physics behavior that cannot be perfectly synchronized between clients. Remote viewers may see slightly different animations, but the roller's broadcasted results are always the source of truth.

Target Success / Dice Pool

Some systems use dice pools where you count successes (dice meeting a condition) rather than summing values.

How It Works:

  • Place the compare point immediately after the dice (before operators): 5d10>=8
  • Each die that meets the condition counts as 1 success
  • Total = number of successes (not sum of values)

Basic Dice Pools:

  • 5d10>=8 - Count dice that rolled 8 or higher
  • 2d6=6 - Count only exact rolls of 6
  • 4d3>1 - Count dice greater than 1
  • 4d3<2 - Count dice less than 2
  • 6d10<=4 - Count dice 4 or lower
  • 2d6<>4 - Count dice not equal to 4 (use <>, not !=)

With Failures:

Add an f modifier to count failures that subtract from successes:

  • 4d6>4f<3 - Success: >4, Failure: <3
  • Total = (successes) - (failures)

Display Format:

  • 5d10>=8: [2, 4, 6*, 3, 8*] = 2 - * marks successes
  • 4d6>4f<3: [2_, 5*, 4, 5*] = 1 - _ marks failures

Combining with Other Operators:

Dice pools work with other operators:

  • 5d10>=8! - Dice pool with exploding dice (explode on max)
  • 5d10>=8r1 - Dice pool with reroll 1s

Operator Order Matters:

  • 5d10>=8 - Dice pool (count ≥8 as successes)
  • 5d10!>=8 - Exploding dice (explode on ≥8, sum values)
  • 5d10>=8! - Dice pool + explosions (count ≥8, explode on 10)

Important Notes:

  • Compare point MUST be immediately after XdY for dice pools
  • If compare point follows ! or r, it's the selector for that operator
  • Failure modifier f must follow a success condition
  • Use <> for "not equal", not !=

Examples:

2d6=6: [4, 6*] = 1                   // only 6 is a success
4d3>1: [1, 3*, 2*, 1] = 2            // >1 is a success
4d3<2: [1*, 3, 2, 1*] = 2            // <2 is a success
5d8>=5: [2, 4, 6*, 3, 8*] = 2        // ≥5 is a success
6d10<=4: [7, 2*, 10, 3*, 3*, 4*] = 4 // ≤4 is a success
4d6>4f<3: [2_, 5*, 4, 5*] = 1        // 2 successes - 1 failure = 1
5d10>=8!: [2, 4, 6*, 3, 8*, 10*, 7*] = 4  // Pool with explosion

Reroll Highest / Lowest

Reroll the N highest or lowest dice after all dice have settled. Unlike r/ro (which reroll individual dice as they land), rh/rl rank all dice together and reroll the selected ones.

Notation: rh[count][condition][:maxIterations] / rl[count][condition][:maxIterations]

  • count - Number of dice to target (default: 1 if omitted)
  • condition - Optional: only reroll if the targeted die meets this condition
  • :N - Optional: maximum number of rerolls per targeted die

Examples:

  • 3d6rh - Roll 3d6, reroll the highest die once
  • 3d6rh2 - Roll 3d6, reroll the two highest dice once each
  • 3d6rl - Roll 3d6, reroll the lowest die once
  • 3d6rl1<3 - Roll 3d6, reroll the lowest die only if it rolled less than 3
  • 3d6rl1<3:2 - Roll 3d6, reroll the lowest die if < 3, up to 2 times

Behavior:

  • Without a condition: the targeted die(s) are always rerolled exactly once
  • With a condition: the targeted die is rerolled while its value still matches the condition (up to maxIterations)

Note: Rank rerolls wait for all dice in the group to settle, then physically despawn and re-throw the targeted dice — just like r/ro rerolls. The results are calculated deterministically so all players see the same final values.

Unique (Coming Soon)

  • 4d6u - Roll 4d6, all results must be unique
  • 4d6uo - Roll 4d6 unique, unlimited re-rolls to ensure uniqueness

Advanced Operators

Dice Model Selection

Assign specific dice from your dice bag by name:

  • 2d6{Red} - Use dice named "Red" from your d6 dice bag
  • 1d12{Fear}+1d12{Hope} - Use "Fear" and "Hope" dice
  • If the named die doesn't exist, falls back to normal cycling through active dice

Roll Descriptions/Labels

Add descriptive labels to your rolls using the # symbol. The # can appear immediately after a dice roll or at the end of the full expression:

  • 3d6 #Fire damage - Label attached directly to a dice roll
  • 3d6-1 #Fire damage - Label at the end of a full expression (after math)
  • (3d6-1)+(3d6-2) #Attack roll - Label after a parenthesized expression
  • 3d6 #Fire + 2d6 #Ice - Multiple rolls with per-roll descriptions
  • 1d20+5#ToHit, 1d8+3#Damage - One label per roll in a comma-separated list
  • (1d6+1#Sword, 1d8+2#Axe)kh1 - One label per entry in a grouped roll

When # appears directly after XdY (before any operators or math), it labels that specific dice group. When # appears after the full expression it labels the entire roll — and in a comma-separated list or a grouped roll, it labels the entry it follows.

A label runs to the end of the notation, stopping at a math operator (+, -, *, /), a comma, or the ) that closes a group it sits inside. So a label cannot itself contain those characters: write #Fire damage, not #Fire, cold damage.

A # always follows what it labels, so it never clashes with the repeat shorthand, where the # comes before the expression it repeats: 6#4d6dl1 #Ability scores repeats 4d6dl1 six times and labels them.

Combined Features

All features can be combined for powerful roll expressions:

  • 3d6{Red} # Fire damage + 2d6{White} # Frost damage - Models and descriptions
  • 2d20dl1{Red} # Attack with advantage - Drop lowest with model and description
  • 4d6dh1 # Ability Score - Drop highest with description

Mathematical Operations

Dice+ supports standard mathematical operations with proper operator precedence:

Multiplication and Division:

  • 2*2d6 - Roll 2d6 and multiply the result by 2
  • 2d6*2 - Roll 2d6 and multiply the result by 2
  • 2d6/2 - Roll 2d6 and divide the result by 2 (rounded down)
  • 2d6+5*10 - Roll 2d6 and add 50 (5×10 is evaluated first)

Parentheses for Order of Operations:

  • 2*(2d6+2d8) - Roll 2d6 and 2d8, sum them, then multiply by 2
  • (4d6kh3)*2 - Roll 4d6 keep highest 3, then multiply the result by 2
  • (2d6+5)*3 - Roll 2d6, add 5, then multiply the total by 3
  • (3d6-1)+(3d6-2) - Two separate d6 groups each with their own modifier

Operator Precedence (standard math rules):

  1. Parentheses () - Evaluated first
  2. Multiplication * and Division / - Evaluated left to right
  3. Addition + and Subtraction - - Evaluated left to right

Display Format:

  • 2*2d6 with rolls [4, 4] → Display: 2*[4, 4] 8 = 16
  • 2d6+5*10 with rolls [3, 4] → Display: [3, 4] 7+50 = 57
  • 2*(2d6+2d8) with rolls [4,4]+[3,7] → Display: 2*([4, 4] 8+[3, 7] 10) = 36

Complex Expressions

Dice notation supports complex mathematical expressions:

  • 2d20 + 1d6 + 5 - Multiple dice types in one roll
  • 2d20kh1 + 1d4 - Combine advantage with additional dice
  • -1d4 - Negative dice (subtract the result)
  • 3d6!1,6 # Wild Magic - Explode on 1 or 6 with description
  • 2d20kh1 + 1d6!5,6 + 3 - Advantage with exploding damage dice
  • 2d8*2 # Doubled damage - Multiply dice results with description
  • (1d6+1d8)*2 + 5 - Complex expression with parentheses

Arbitrary Dice

Use d{values} to assign custom integer results to each face, following AnyDice's arbitrary dice notation. Entries stay in the order written; repeated values give that result more chances to occur.

Notation Description
d{0,0,0,1,1,2} A d6: faces 1–3 score 0, faces 4–5 score 1, and face 6 scores 2
2d{1..6} + 3 Two dice with faces 1, 2, 3, 4, 5, 6, plus 3
d{-2..2,5} A d6 with values −2, −1, 0, 1, 2, 5
d{10,20,30,40,50} Five equally likely results, resolved by the random fallback

Ranges include both endpoints and increase by 1. Lists must be nonempty and contain integers or ascending ranges. The expanded face count selects the physical die model when available (including the usual paired d10s for 100 faces); other counts use the existing random fallback.

Note: Physical face labels

Physical dice retain their usual face labels, while results and totals use the custom values.

Arbitrary dice support counts, arithmetic, and grouped rolls. Per-die operators (keep/drop, reroll, explode, min/max, and success comparisons) are not supported.

Fudge Dice (dF Notation)

For Fate / Fudge system games. dF (or df) rolls a six-sided die showing two +, two blank, two - faces. Each die contributes −1, 0, or +1 to the total.

Notation Description Result Range
dF / 1dF One Fudge die −1 to +1
4dF Four Fudge dice (standard Fate ladder) −4 to +4
4dF + 2 Four Fudge dice with a +2 skill modifier −2 to +6
1d20 + 1dF Mixed roll varies

Mapping: Each Fudge die is physically a d6. Faces 1 and 4 → −1; faces 2 and 5 → 0; faces 3 and 6 → +1. The visible glyph on the die is + / blank / -.

Restrictions: Fudge dice support only count and arithmetic (+, -, *, /). Operators like ! (explode), kh/kl (keep), r (reroll), and dice pool comparisons are not allowed on dF.

Dicebag preview: When editing a d6 in the dice bag, a "Show as Fudge die (preview)" toggle previews the Fudge texture without changing the saved die. Rolling Fudge always uses dF notation.

Table Roll Notation (T Notation)

Use T followed by digits to roll multiple dice as place values — perfect for rolling on lookup tables in TTRPGs like Warhammer, OSR games, and others that use "roll on a D66 table" style mechanics.

Each digit after T specifies the die type for that place (hundreds, tens, ones, etc.):

Notation Dice Rolled Result Range
T66 d6 (tens) + d6 (ones) 11 – 66
T88 d8 + d8 11 – 88
T468 d4 + d6 + d8 111 – 468
T1000 d10 + d10 + d10 1 – 1000
T108 d10 (tens) + d8 (ones) 1 – 98
T6666 four d6s 1111 – 6666

Special digit rules:

  • 10 as a consecutive pair → d10 (e.g., T108 = d10 for tens, d8 for ones)
  • 0 alone (not at start) → d10 (e.g., T1000 = three d10s)
  • Single digits 1–9 → die with that many sides (e.g., T66 = two d6s)

d10 convention: When a d10 shows 10, it counts as 0 for the place value — the same convention as percentile dice. For all-d10 table rolls, a result of all-zeros equals the maximum (e.g., T1000 with three 10s → 1000, T100 with two 10s → 100).

Examples:

  • T66 — Roll on a Warhammer-style 36-entry table (results: 11, 12, ..., 66)
  • T1000 — Roll on a 1000-entry table (same as three d10s / d1000)
  • T468 — Roll on a custom 64-entry table with varied dice

Table rolls can be combined with math modifiers:

  • T66+10 — Table roll result plus 10
  • T66 Magic Item — Named table roll

Grouped Rolls

Roll several complete rolls at once and keep or drop whole results by their totals. Wrap comma-separated rolls in parentheses and add a group operator after the closing parenthesis:

Notation Meaning
(2d6, 1d12)kh1 Roll both; keep the higher total
(2d6, 1d12, 1d8)kh2 Keep the two highest totals and add them
(2d6, 1d12)kl1 Keep the lower total
(2d6, 1d12, 1d8)dh1 Drop the highest total, add the rest
(2d6, 1d12, 1d8)dl1 Drop the lowest total, add the rest
(2d6, 1d12)+5 No operator inside math: totals are simply added
  • Every die rolls physically — dice from dropped rolls stay visible but grayed out.
  • Each entry is a full roll: operators and math work inside, e.g. (1d4r<1, 1d6r<1, 2d8r<1)kh1 or (2d6+3, 1d12)kh1+5.
  • Entries can be labelled with #, e.g. (1d6+1#Sword, 1d8+2#Axe)kh1.
  • The number after the operator is optional and defaults to 1 (kh = kh1).
  • If totals tie, the roll written first wins.
  • Groups cannot be nested inside other groups.
  • A bare parenthesized list with no group operator that is the whole notation, e.g. (2d6, 1d12), is treated as a multi-roll — each entry becomes its own independent result.
  • One quirk: a plain number directly after an operator value list is read as part of the list — in (1d6r1,2, 3)kh1 the 3 joins r1,2. Reorder the entries ((3, 1d6r1,2)kh1) or write 3+0.

Multiple Rolls at Once

Separate complete rolls with commas (no group operator) to roll them all together while keeping each result independent. All dice hit the tray at the same time, and the results panel shows one row per roll — each with its dice values, notation, and its own total.

Notation Meaning
1d12, 2d6, 2d20kh1 Three independent rolls, one row each
2d20kh1+5, 2d20kh1+3, 2d20kh1+4 Three attack rolls with different bonuses
(2d20kh1+5, 2d20kh1+3) Same — outer parentheses are optional
1d20+7, (2d6, 1d12)kh1 An attack plus a grouped damage roll as separate results
  • Each entry supports the full notation syntax: operators, math, tags, table rolls, and grouped rolls.
  • There is no combined grand total — every row stands on its own.
  • Roll history stores the whole multi-roll as one entry; rerolling repeats all of it.
  • To label a row, end it with #Label (e.g. 1d20+5#ToHit, 1d8+3#Damage); the label appears next to that row's notation. A # placed directly after the dice (1d20#Attack+7, 2d6#Damage) still labels just that dice group and shows next to its values.
  • For extensions using the broadcast API: total in the roll-result message is the sum of all rows; per-row values can be recomputed from groups and the notation, and rollSummary lists each row as ... = total segments separated by |, each prefixed with Label: when the row is labelled.

Repeating a Roll

Write X# in front of an expression to roll it X times. It is pure shorthand: 6#4d6dl1 is identical to typing 4d6dl1, 4d6dl1, 4d6dl1, 4d6dl1, 4d6dl1, 4d6dl1, right down to the six independent result rows.

Notation Meaning
6#4d6dl1 Classic stat generation: six ability scores
3#1d20+5 Three separate attack rolls
2#1d20, 1d8 Two d20 rows plus a d8 row — repeats mix freely with commas
4#T66 Roll four times on the same table
(3#1d6)kh1 Three d6 as a grouped roll: keep the highest
  • The repeated expression can be anything a comma entry can be: operators, math, tags, descriptions, table rolls, and grouped rolls.
  • The count must sit directly against the # (6#4d6dl1, not 6 # 4d6dl1), which is what keeps it distinct from a # label.
  • A description on the repeated roll is repeated too: 6#4d6dl1 #Ability score labels every row.
  • Repeat counts run from 1 to 100. Remember the dice all spawn at once — 20#8d6 is 160 dice in the tray.

Extension Integration

Dice+ provides broadcast channels for integration with other Owlbear Rodeo extensions using the OBR Broadcast API.

Ready Check Channel

Before sending roll requests, external extensions can check if Dice+ is loaded and ready to accept requests.

Channel: dice-plus/isReady

How It Works:

  1. Your extension sends a ready check request on the dice-plus/isReady channel
  2. Dice+ automatically responds on the same channel when it's ready
  3. Your extension listens for the response to confirm Dice+ is available

Request Message Structure:

{
  requestId: string;      // Unique request identifier (to match responses)
  timestamp: number;      // Request timestamp
}

Response Message Structure:

{
  requestId: string;      // Matches the request requestId
  ready: true;            // Always true when Dice+ responds
  timestamp: number;      // Response timestamp
}

Example:

import OBR from "@owlbear-rodeo/sdk";

async function checkDicePlusReady(): Promise<boolean> {
  const requestId = crypto.randomUUID();

  return new Promise((resolve) => {
    const unsubscribe = OBR.broadcast.onMessage("dice-plus/isReady", (event) => {
      const data = event.data;

      // Check if this is a response (not a request)
      if ('ready' in data && data.requestId === requestId) {
        unsubscribe();
        resolve(true);
      }
    });

    // Send ready check request
    OBR.broadcast.sendMessage("dice-plus/isReady", {
      requestId,
      timestamp: Date.now()
    }, { destination: 'ALL' });

    // Timeout after 1 second if no response
    setTimeout(() => {
      unsubscribe();
      resolve(false);
    }, 1000);
  });
}

// Use it before sending roll requests
const isDicePlusReady = await checkDicePlusReady();
if (!isDicePlusReady) {
  console.error("Dice Plus extension not found!");
  return;
}

Notation Validation Channel

Check whether a dice notation string is valid before sending a roll request. No dice are rolled and nothing is logged to stats or history — this is a pure parse check.

Channel: dice-plus/validate-notation

How It Works:

  1. Your extension sends a validation request on the dice-plus/validate-notation channel
  2. Dice+ validates the notation with its parser and responds on the same channel
  3. Requests and responses share the channel — responses are the messages that carry a valid field
  4. In a multi-client room every Dice+ client responds; results are identical, so match on requestId and use the first response

Request Message Structure:

{
  requestId: string;      // Unique request identifier (to match responses)
  notation: string;       // The dice notation to validate (e.g. "4d6kh3+2")
  timestamp: number;      // Request timestamp
}

Response Message Structure:

{
  requestId: string;      // Matches the request requestId
  notation: string;       // The notation from the request, echoed back
  valid: boolean;         // Whether the notation parsed successfully
  error?: {               // Present only when valid is false
    message: string;      // Human-readable parse error
    position: number;     // 0-based character index where parsing failed
  };
  timestamp: number;      // Response timestamp
}

Example:

import OBR from "@owlbear-rodeo/sdk";

async function validateNotation(notation: string): Promise<{ valid: boolean; error?: string }> {
  const requestId = crypto.randomUUID();

  return new Promise((resolve) => {
    const unsubscribe = OBR.broadcast.onMessage("dice-plus/validate-notation", (event) => {
      const data = event.data;

      // Check if this is a response (not a request)
      if ('valid' in data && data.requestId === requestId) {
        unsubscribe();
        resolve({ valid: data.valid, error: data.error?.message });
      }
    });

    // Send validation request
    OBR.broadcast.sendMessage("dice-plus/validate-notation", {
      requestId,
      notation,
      timestamp: Date.now()
    }, { destination: 'ALL' });

    // Timeout after 1 second if no response
    setTimeout(() => {
      unsubscribe();
      resolve({ valid: false, error: "Dice+ did not respond" });
    }, 1000);
  });
}

const check = await validateNotation("4d6kh3+2");
if (!check.valid) {
  console.error("Invalid notation:", check.error);
}

Roll Request Channel

Other extensions can send dice roll requests to Dice+ using the broadcast channel.

Channel: dice-plus/roll-request

Message Structure:

{
  rollId: string;           // Unique roll identifier (save this to match results)
  playerId: string;         // OBR player ID
  playerName: string;       // Player display name
  rollTarget: 'everyone' | 'self' | 'dm' | 'gm_only';  // Who sees the roll
  diceNotation: string;     // Standard dice notation (e.g., "2d20kh1+5")
  showResults: boolean;     // Show default popup (false = handle in your UI)
  timestamp: number;        // Request timestamp
  source: string;           // Your extension identifier (e.g., "my-extension-id")
}

Important Notes:

  • source field: Must be your extension's unique identifier. This is used to create dedicated result and error channels for your extension.
  • Result channels: Results are sent to your dedicate channel:
    • {source}/roll-result - Your extension's dedicated channel
  • Error channel: Errors will be sent to {source}/roll-error
  • Stats & History: ALL rolls (internal and external) are automatically logged to Dice+ stats and history to be viewed in dice bag stats panel no matter the source of the roll and to track dice statistics
  • Popup behavior:
    • Internal rolls always show the Dice+ popup
    • External rolls show popup only if showResults: true

Roll Targets:

  • 'everyone' - All players see the dice roll and results
  • 'self' - Only the roller sees the dice and results
  • 'dm' (GM/Self) - Both the GM and the roller see the dice and results
  • 'gm_only' (GM Only) - Only the GM sees the dice and results. The roll always executes on the GM's screen regardless of who initiated it. The original player's identity is preserved in the result.

Example:

import OBR from "@owlbear-rodeo/sdk";

const MY_EXTENSION_ID = "my-extension-id"; // Use your extension's unique identifier
const rollId = `roll_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;

// Send roll request
await OBR.broadcast.sendMessage("dice-plus/roll-request", {
  rollId,
  playerId: await OBR.player.getId(),
  playerName: await OBR.player.getName(),
  rollTarget: 'everyone',
  diceNotation: '2d20kh1+5',
  showResults: false,  // Handle results in your own UI
  timestamp: Date.now(),
  source: MY_EXTENSION_ID  // Use your extension ID
}, { destination: 'ALL' });

// Listen for results on YOUR extension's channel
OBR.broadcast.onMessage(`${MY_EXTENSION_ID}/roll-result`, (event) => {
  const result = event.data;
  // Handle your roll result
  if (result.rollId === rollId) {
    console.log("Roll complete:", result.result.totalValue);
  }
});

// Listen for errors on YOUR extension's channel
OBR.broadcast.onMessage(`${MY_EXTENSION_ID}/roll-error`, (event) => {
  const error = event.data;
  if (error.rollId === rollId) {
    console.error("Roll failed:", error.error);
  }
});

Roll Result Channel

Dice+ sends roll results to source-specific broadcast channels.

Channel: {source}/roll-result (where {source} is the value you provided in the roll request)

Message Structure:

{
  rollId: string;           // Matches the request rollId
  playerId: string;         // Who rolled
  playerName: string;       // Display name
  rollTarget: 'everyone' | 'self' | 'dm' | 'gm_only';  // Roll target
  timestamp: number;        // Original timestamp
  result: {
    rollId: string;         // Roll identifier
    diceNotation: string;   // Original notation
    totalValue: number;     // Final sum (correctly handles *, /, ())
    rollSummary: string;    // E.g., "2d6+3 = 11" or "2*[4,4] 8 = 16"
    groups: DiceGroup[];    // Grouped results by notation order
  };
}

// DiceGroup structure
interface DiceGroup {
  description?: string;     // E.g., "Fire" from "# Fire"
  diceModel?: string;       // E.g., "Red" from "{Red}"
  diceType: string;         // E.g., "d6", "d20"
  dice: DiceResult[];       // Individual dice in this group
  total: number;            // Total of kept dice in this group
  isNegative?: boolean;     // Whether this group is subtracted
  isCondensed?: boolean;    // True when the group was condensed (see below)
  condensedCount?: number;  // Original dice count before condensation
}

Message size limit: OBR caps broadcast messages at 64KB. If a roll result would exceed that (very large rolls or many multi-roll rows), Dice+ condenses each group to a single aggregate entry — dice contains one item whose value is the group total, with isCondensed: true and condensedCount set. Group totals and totalValue are always preserved.

Complete Example with Data:

Example 1: Advantage with Subtraction - 2d20kh1-1d4 (advantage on d20, subtract 1d4):

const MY_EXTENSION_ID = "my-extension-id";

// Request sent
const rollId = 'roll_1760043220783_abc123';
await OBR.broadcast.sendMessage("dice-plus/roll-request", {
  rollId,
  playerId: '81e7413f-920c-4639-bd9d-1895ac156cf1',
  playerName: 'Gandalf',
  rollTarget: 'everyone',
  diceNotation: '2d20kh1-1d4',
  showResults: false,
  timestamp: 1760043220783,
  source: MY_EXTENSION_ID  // Your extension ID
}, { destination: 'ALL' });

// Result received on channel: my-extension-id/roll-result
{
  rollId: 'roll_1760043220783_abc123',
  playerId: '81e7413f-920c-4639-bd9d-1895ac156cf1',
  playerName: 'Gandalf',
  rollTarget: 'everyone',
  timestamp: 1760043220783,
  result: {
    rollId: 'roll_1760043220783_abc123',
    diceNotation: '2d20kh1-1d4',
    totalValue: 15,                    // Final result: 18 - 3 = 15
    rollSummary: '[18] 18 - 3 = 15',  // Human-readable summary
    groups: [
      {
        diceType: "d20",
        dice: [
          { diceId: 'dice_001', rollId: 'roll_abc123', diceType: 'd20', value: 18, kept: true },
          { diceId: 'dice_002', rollId: 'roll_abc123', diceType: 'd20', value: 7, kept: false }
        ],
        total: 18,                      // Total of kept dice only (18)
        isNegative: false
      },
      {
        diceType: "d4",
        dice: [
          { diceId: 'dice_003', rollId: 'roll_abc123', diceType: 'd4', value: 3, kept: true }
        ],
        total: 3,
        isNegative: true                // This group is subtracted
      }
    ]
  }
}

Understanding the Results:

  • groups organizes all dice into their notation groups - even single dice get their own group
  • Each group includes ALL dice rolled for that group (both kept and dropped dice)
  • Each group's dice array contains all dice with a kept flag indicating if they count toward the total
  • Each group's total field is the sum of only the kept dice in that group
  • totalValue is the final calculated result after all modifiers
  • For 2d20kh1-1d4: rolled 18 and 7 on the d20s (kept 18), rolled 3 on the d4, final: 18 - 3 = 15

Example 2: Multiplication - 2*2d6 (roll 2d6 and multiply by 2):

// Result received on channel: my-extension-id/roll-result
{
  rollId: 'roll_1760043220783_xyz789',
  playerId: '81e7413f-920c-4639-bd9d-1895ac156cf1',
  playerName: 'Gandalf',
  rollTarget: 'everyone',
  timestamp: 1760043220783,
  result: {
    rollId: 'roll_1760043220783_xyz789',
    diceNotation: '2*2d6',
    totalValue: 16,                      // Final result: 2 * 8 = 16
    rollSummary: '2*[4, 4] 8 = 16',     // Shows the multiplication
    groups: [
      {
        diceType: "d6",
        dice: [
          { diceId: 'dice_001', rollId: 'roll_xyz789', diceType: 'd6', value: 4, kept: true },
          { diceId: 'dice_002', rollId: 'roll_xyz789', diceType: 'd6', value: 4, kept: true }
        ],
        total: 8,                         // Total of dice (4 + 4 = 8)
        isNegative: false
      }
    ]
  }
}

Example 3: Descriptions and Dice Models - 3d6{Red} # Fire + 2d6{White} # Ice:

// Result received on channel: my-extension-id/roll-result
{
  rollId: 'roll_1760043220783_xyz789',
  playerId: '81e7413f-920c-4639-bd9d-1895ac156cf1',
  playerName: 'Gandalf',
  rollTarget: 'everyone',
  timestamp: 1760043220783,
  result: {
    rollId: 'roll_1760043220783_xyz789',
    diceNotation: '3d6{Red} # Fire + 2d6{White} # Ice',
    totalValue: 19,
    rollSummary: '[1, 3, 6] 10 Fire + [2, 3, 4] 9 Ice = 19',
    groups: [
      {
        description: "Fire",
        diceModel: "Red",
        diceType: "d6",
        dice: [
          { diceId: 'dice_001', rollId: 'roll_xyz789', diceType: 'd6', value: 1, kept: true },
          { diceId: 'dice_002', rollId: 'roll_xyz789', diceType: 'd6', value: 3, kept: true },
          { diceId: 'dice_003', rollId: 'roll_xyz789', diceType: 'd6', value: 6, kept: true }
        ],
        total: 10,
        isNegative: false
      },
      {
        description: "Ice",
        diceModel: "White",
        diceType: "d6",
        dice: [
          { diceId: 'dice_004', rollId: 'roll_xyz789', diceType: 'd6', value: 2, kept: true },
          { diceId: 'dice_005', rollId: 'roll_xyz789', diceType: 'd6', value: 3, kept: true },
          { diceId: 'dice_006', rollId: 'roll_xyz789', diceType: 'd6', value: 4, kept: true }
        ],
        total: 9,
        isNegative: false
      }
    ]
  }
}

The groups field makes it easy to:

  • Access dice results organized by their notation order
  • Get the description and dice model name for each group
  • Calculate group totals without parsing individual dice
  • Build custom UI displays that match the roll notation

Roll Error Channel

Dice+ sends error messages to source-specific channels when a roll fails.

Channel: {source}/roll-error (where {source} is the value you provided in the roll request)

Message Structure:

{
  rollId: string;           // Matches the request rollId
  error: string;            // Error message
  notation: string;         // Notation that failed
}

Example Listener:

const MY_EXTENSION_ID = "my-extension-id";

OBR.broadcast.onMessage(`${MY_EXTENSION_ID}/roll-error`, (event) => {
  const error = event.data;

  // Match by rollId if needed
  if (error.rollId === rollId) {
    console.error(`Roll failed: ${error.error}`);
  }
});

Made with ❤️ for the Owlbear Rodeo community