Overview
You will learn
- Why tutorial variables exist and when to reach for them
- How to declare a
variables:block in your source frontmatter - How to reference a variable with a
{{var:<name>}}token in prose, inline code, and fenced code blocks - How to pre-fill a variable from the reader’s identity with a
defaultregex - How values persist per-reader and carry across the tutorials in a mission
Prerequisites
- Familiarity with the basic tutorial markdown structure (see writing-tutorials.md)
- A tutorial source repo under the
sap-tutorialsorg
Steps
This tutorial is self-demonstrating. It declares one variable,
groupNumber, and uses it throughout. Type a value into the Your workshop group number field at the top of the page and watch every occurrence below — including inside the code blocks — update live as you read.
Workshop and hands-on tutorials often contain a value that is different for every reader — most commonly a group or seat number handed out at the start of a session. The old approach was a manual placeholder like XXX that the reader was told to find-and-replace everywhere:
Create a space named
dev-XXX, then a userda164-XXX@education.cloud.sap, then a projectproduct-advisor-XXX.
The reader has to remember to swap XXX in every copy-pasted command. Miss one and the exercise breaks — and the mistake usually surfaces several steps later, where it is hardest to debug.
Tutorial variables fix this. The author declares the value once, the reader types it once into a field at the top of the page, and every marked occurrence — prose, inline code, and fenced code blocks alike — updates live. The reader copy-pastes commands with their own value already baked in, with nothing to remember.
When not to use a variable: values that are the same for every reader (a fixed service URL, a product name) are just prose — don’t wrap them. Variables are specifically for the per-reader value.
Add a variables: key to your tutorial’s frontmatter. Each entry is one variable. This tutorial’s own declaration looks like this:
variables:
- name: groupNumber # identifier + the body-token key; must be unique
label: Your workshop group number # the field label the reader sees
type: string # string | number
maxLength: 3 # optional: caps input length + drives validation
required: true # optional: marks the field required
placeholder: XXX # optional: shown until the reader types a valueField rules:
name(required) — must match[A-Za-z][A-Za-z0-9_]*and be unique within the tutorial. This is also the key you use in body tokens.label(required) — a non-empty string; the prompt shown above the input field.type(required) —stringornumber. Nothing else is valid.maxLength,required,placeholder— all optional.placeholderis what every unfilled occurrence shows before the reader types anything (your call —XXX, a sample like001, or anything else).
A malformed variables: block is a hard build failure — a bad declaration can never reach readers. The build fails on a duplicate name, an invalid type, a default.from off the allowlist, an uncompilable default.pattern, or a body token that references a variable you never declared. (A declared-but-unused variable is allowed — it is only a soft warning.)
Write {{var:<name>}} (using your declared variable’s name) anywhere you want the value substituted. The same token works in three contexts — this step uses all three with our groupNumber variable, so change the field at the top of the page and watch each one update.
In prose: your workshop group number is XXX, and it appears inline in this sentence.
In inline code: your space is named dev-TUTVARxgroupNumberx and your user is da164-TUTVARxgroupNumberx@education.cloud.sap.
In a fenced code block — the whole point, so the reader can copy-paste a ready-to-run command:
cf create-space dev-
cf create-user da164-@education.cloud.sap
cf set-space-role da164-@education.cloud.sap <org> dev- SpaceDeveloperThe token is intentionally neutral:
- It renders as literal text on GitHub and reads cleanly to anyone (or any AI) looking at the raw markdown source.
- The
var:prefix keeps it clear of Hugo shortcode delimiters, so the build leaves it alone until the substitution step. - It behaves identically inside and outside code fences — you never need a different syntax for code.
Tip: build the token right into the surrounding text, e.g.
dev-TUTVARxgroupNumberx, notdev- TUTVARxgroupNumberx. The substitution replaces exactly the token, so no stray spaces end up in a copied command.
Sometimes the per-reader value can be derived rather than typed — for example, when the group number is already embedded in the reader’s logged-in email (da164-007@education.cloud.sap). Add an optional default block to pull it in automatically:
variables:
- name: groupNumber
label: Your workshop group number
type: string
maxLength: 3
placeholder: XXX
default:
from: email # one of: email | id | givenName | familyName
pattern: 'da164-(\d+)@' # capture group 1 becomes the default valueHow it behaves:
- On first load, if the reader has no stored value yet, the platform evaluates
patternagainst the named identity field and pre-fills capture group 1. Forda164-007@education.cloud.sapthe field starts at007. frommust be one of the allowlist —email,id,givenName,familyName— read from the reader’s existing session. Anything else is a build failure.- The field always renders and is always overridable — the default is a convenience, not a lock. If the reader is anonymous, or the pattern doesn’t match, the default simply doesn’t fire and the
placeholdershows instead.
Leave default off entirely (as this tutorial does) when the value can’t be derived and the reader should just type it.
The reader’s entered value is stored client-side only — there is no server-side per-user rendering and nothing is persisted on the server. That keeps the feature privacy-clean: the same built HTML is served to everyone, and substitution happens in the reader’s browser.
Two things follow from that, both worth telling your readers:
- Values survive a browser restart. A reader who types
007and comes back tomorrow still sees007. - Values carry across a mission. If
groupNumberis used in several tutorials in the same group or mission, the value typed in the first one is already filled in the rest. Carry-over is by variable name within the mission context — so use the samenameacross a mission’s tutorials when you mean the same value, and a different name when you don’t.
Authoring consequence: because carry-over keys on the variable
name, keep names consistent across a mission (groupNumbereverywhere, notgroupin one tutorial andgroupNumin the next) or readers will be prompted again.
Variables ride through the normal author workflow — no special publish step. Before you open a PR:
- Preview on the QA channel. Push to your
-Contributionrepo and open the tutorial at/tutorials-qa/<your-slug>. Confirm the field appears at the top, and that typing a value updates prose, inline code, and the fenced code blocks. - Copy-paste test. Type a value, then copy a command out of a fenced block and confirm your value is baked in — this is the behavior that actually matters to a workshop attendee.
- Check the build didn’t fail. A malformed
variables:block or a{{var:<name>}}token whose<name>you never declared fails the build loudly; a green build means your declarations and tokens are consistent.
For the full set of authoring features, see the Tutorial Platform Feature Cookbook and writing-tutorials.md.
Resources
Discussion
Share feedback on this tutorial or join the conversation in SAP Community.