Code mode (Roll20 style)

Build your system by writing HTML, CSS and sheet workers like in Roll20: transpiler, isolated sandbox, roll buttons and round-tripping back to code.

Code mode lets you build a system by writing, with the same words you used in Roll20's character sheet builder: attr_, @{}, repeating_, rolltemplate and sheet workers. Your code is not run directly: it's compiled to the Roliando engine, so you get real fields, layout, rolls and automation that work with the rest of the app (combat, tokens, chat, permissions).

It suits you if:

  • You come from Roll20 and want to bring your sheet over without building it again by hand.
  • You'd rather write code than drag widgets.
  • You want to touch the CSS or automate calculations with JavaScript.

You don't need to use it for everything: you can transpile your HTML once and carry on in the visual builder, or the other way around (generate the code from a system you already had).


How to open it

  1. Go to Creations -> your system -> Edit (the classic skin calls it Creations as well).
  2. In the header click Code.

You'll see five tabs: HTML, CSS, Sheet Workers, Roll Template and Glossary, with a live preview next to them.

The CSS and the roll template are the same as the builder's Style tab: edit them in either place.


First of all: how safe it is

Other people open your system in their browser, so three rules always apply:

PieceWhat really happens
HTMLNever shown raw. It's transpiled to fields + layout (widgets) and treated as data input, never injected as a template.
CSSIt's filtered and scoped to your sheet (it never affects the rest of the app). It only allows images and fonts uploaded by you.
Sheet WorkersThey run in an isolated sandbox (QuickJS): no access to the browser, no network, no other people's data.

This also means some Roll20 features can't be replicated exactly (a <div> with free-form layout, for example): your markup is translated to Roliando widgets. In return, the sheet works on mobile, in the VTT and with the rules engine.


HTML tab

Here you paste or write the markup. It has three buttons:

  • Transpile to fields + layout: converts the HTML into a real system.
  • Split full sheet: if you pasted the whole Roll20 file (with <style> and <script type="text/worker"> inside), it sends each part to its tab.
  • Generate from the system: the reverse. It writes the HTML from the fields and layout you already have.

What the transpiler recognizes

You write (Roll20)You get (Roliando)
<input type="number" name="attr_fuerza" value="10">Number field fuerza with default value 10
<input type="text" name="attr_nombre">Text field
<textarea name="attr_notas">Text field (multiline)
<input type="checkbox" name="attr_escudo">Yes/no field
<select name="attr_clase"> + <option>Select field with its options
Group of <input type="radio" name="attr_x">Select field (the options are the values)
attr_hp and attr_hp_max (or `name="hp\max"`)A single resource field (current/maximum)
<span name="attr_mod">Display-only number field
<fieldset class="repeating_ataques">Dynamic rows widget with its columns
<button type="roll" value="/roll 1d20+@{fuerza}">Roll button + its rollable field
<h1>...<h4>, <legend>Section separator

The label of each field is taken, in this order: from the <label> wrapping it, from the title/placeholder attribute, from the preceding text, and failing that, from the key itself.

Minimal example

<h3>Atributos</h3>
<label>Fuerza <input type="number" name="attr_fuerza" value="10"></label>
<label>Destreza <input type="number" name="attr_destreza" value="10"></label>
<span name="attr_mod_fuerza"></span>

<input type="number" name="attr_pv" value="20">
<input type="number" name="attr_pv_max" value="20">

<label>Clase</label>
<select name="attr_clase" title="Clase">
  <option value="guerrero">Guerrero</option>
  <option value="mago">Mago</option>
</select>

<button type="roll" name="roll_iniciativa" value="/roll 1d20+@{destreza}">Iniciativa</button>

On clicking Transpile you'll see a summary (how many fields, resources, lists, rolls) and the warnings about what was approximated. Then you choose:

  • Replace fields and layout: leaves the system exactly as the HTML says. It asks for confirmation because it deletes whatever was there.
  • Add what's new: keeps your fields and only adds the ones that were missing.

After applying, fine-tune in the Fields and Layout tabs as with any system.

Bringing your whole Roll20 sheet

  1. Open your sheet in Roll20 and copy the full HTML (if you have a single file with styles and workers inside, that's best).
  2. Paste it in the HTML tab.
  3. Click Split full sheet: the <style> goes to CSS and each <script type="text/worker"> to Sheet Workers. Any other <script> is discarded (it never runs) and you're told how many.
  4. Click Transpile to fields + layout and apply.

You now have the fields, layout, style and automation of your original sheet.

To bring characters (your players' filled-in sheets rather than the template), use the separate importer on the sheets screen.


CSS tab

Write regular CSS. Underneath, each selector is scoped to your sheet.

  • .charsheet (the root in Roll20) is equivalent to the sheet's root here: you can use it the same way.
  • Your .sheet-whatever classes are kept.
  • html, body and :root are also translated to the sheet's root.

You also have stable hooks to style Roliando's pieces:

SelectorWhat it is
.sys-sheetThe whole sheet
.sys-tabA tab's grid
.sys-widgetAny widget card
.sys-widget-stat_blockAbility cards (and so on for each type)
.sys-sheet.sys-roll-cardA roll's result card (compound, no space)
.sys-roll-message[data-kind="critico"]The message by result: critico, exito, fallo, pifia
.charsheet { background: #101018; }
.sheet-stats input { text-align: center; }
.sys-widget-stat_block { border-color: #b8860b; }
.sys-roll-message[data-kind="critico"] { font-weight: 700; }

What gets filtered: non-visual properties, @import, external url(), expression, javascript: and odd selectors are discarded. position: fixed becomes absolute so nothing covers the app. You'll see the exact list of what was discarded under the editor.

Images and fonts: for safety only the ones you upload are allowed. Upload them from the builder's Style tab and click them to insert them (a font comes in already as a @font-face ready to use).


Sheet Workers tab

Automation with JavaScript, same as in Roll20:

on("change:fuerza", function() {
  getAttrs(["fuerza"], function(v) {
    setAttrs({ mod_fuerza: Math.floor((Number(v.fuerza) - 10) / 2) });
  });
});

Click Test with sample data to run it on the spot. You'll see which fields would change, whatever you print with console.log and any error.

What you have available

FunctionWhat it does
on("change:clave", fn)Reacts to a field changing. Accepts several separated by spaces.
on("sheet:opened", fn)Fires when the sheet is recalculated.
getAttrs(["a","b"], fn)Reads fields. The callback receives an object with their values.
setAttrs({ a: 1 })Writes fields.
getSectionIDs(sec, fn)Returns an empty list (dynamic rows aren't iterated yet).
console.log(...)Shows up in the test panel.
Math, JSON, String, Number...The standard JavaScript objects.

What does NOT exist inside (on purpose)

fetch, XMLHttpRequest, setTimeout, document, window, process, require, WebAssembly. There's no network, no DOM, and no way to see other people's data. If your worker uses them, it errors inside the sandbox and everything else keeps working.

Limits

  • Time: if a worker hangs (a while(true)), it's stopped automatically and the sheet doesn't freeze.
  • Memory: 16 MB per run.
  • Cascade: if a setAttrs triggers another on("change:..."), it chains up to 8 rounds.
  • Values: setAttrs only stores numbers, text and yes/no. Objects and arrays are ignored.

When they run in the game

On the real sheet they recalculate automatically when something changes and when the sheet opens. Only whoever can edit (the owner or the GM) recalculates and saves; everyone else sees the already saved values.

Tip: write idempotent workers (ones that give the same result every time with the same input data). A worker that does setAttrs({ x: x + 1 }) would recalculate in a loop; there's a cap that cuts it off, but the result won't make sense.


Roll Template tab

This is your <rolltemplate>: how the card looks when someone rolls. It's Markdown with values from the result.

**{{etiqueta}}** — sacó **{{total}}**
{{#if critico}}¡CRÍTICO!{{else}}{{#if pifia}}pifia{{/if}}{{/if}}
ValueWhat it brings
{{total}}The final result
{{etiqueta}}Name of the roll
{{resultado}}Success / Failure / Critical / Fumble
{{critico}}, {{pifia}}, {{exito}}, {{fallo}}1 or 0, for use with {{#if}}
{{objetivo}}The threshold, if there was one
{{#each dados}}{{this}} {{/each}}The dice one by one

You can also use any field from the sheet, and style it with CSS via .sys-roll-message.


References: @{} and company

You can write references like in Roll20 and they work the same:

Roll20Equivalent toWhere it's valid
@{fuerza}{{fuerza}}Live text, roll template, button formulas
`@{selected\hp} / @{target\ca}`{{hp}} / {{ca}}Same
`@{hp\max}`{{hp_max}}Same

Both syntaxes work; use whichever you prefer.


Roll buttons, in detail

A <button type="roll" value="/roll 1d20+@{fuerza}+2">Ataque</button> becomes:

  • A rollable field of the button's own (called roll_ataque), with the die (1d20) and the bonus (fuerza + 2) already set.
  • A button in the layout that fires it.

It's a simple roll: it gives the total, without success/failure (the GM sets the difficulty, like /roll). If you want a threshold, criticals or roll modes, adjust the field in Fields or the global rules in Roll rules.

The button doesn't touch your abilities: it creates its own field, so two buttons that use the same ability don't step on each other.


Quick glossary

In Roll20In Roliando
attr_fuerzaField fuerza
attr_hp + attr_hp_maxResource field (current/max)
@{fuerza}{{fuerza}}
repeating_ataquesDynamic rows widget
<rolltemplate>Roll template
sheet-workerAutomation (sandbox)
.charsheet.sys-sheet (sheet root)

The same glossary is in the editor's Glossary tab.


Going back from the system to code

Generate from the system writes the HTML from the current fields and layout. Use it to open a system you built in the visual builder in code mode, edit it as text and transpile it back.

The round trip preserves key, type, label, dropdown options, resources with their maximum and the key of roll buttons.


Known limits

Things that are not translated yet (the transpiler itself warns you when it detects them):

  • %{habilidad} (Roll20 rolls by name).
  • Roll20 tabs (those <input type="radio" class="sheet-tab">): everything lands in a single tab. Reorganize it later in Layout.
  • A Roliando derived field comes out as a <span> when generating code: Roll20 has no derived fields (workers do that there), so on the way back it's imported as a number.
  • Inside dynamic rows, per-row formulas and roll buttons are placed but aren't wired up on their own.
  • Free-form layout (<div> with classes and positions): your markup is translated to widgets, not copied pixel by pixel.

Common problems

"No field was recognized" Your HTML has no name="attr_...". The transpiler only treats those as fields.

"The worker doesn't change anything" Check that the event name matches the field's key (not its label): on("change:fuerza") works if the field is fuerza. Click Test to check.

"My CSS isn't applied" Look at the warnings under the editor: the property may not be allowed or the url() may point outside. Upload the image from Style and insert it again.

"I transpiled and lost fields" You used Replace. Use Add what's new to keep what you already had.

"The roll doesn't give success or failure" A Roll20 /roll is a simple roll. Configure the threshold on the field or in the system's roll rules.


Maximum sizes

HTML 30,000 characters - Sheet Workers 30,000 - CSS 20,000 - Roll template 2,000.

All Roliando guides