reMarkable templates are JSON files that describe page layouts using a tree of groups, paths, and text items. Values throughout the tree can be numeric literals or arithmetic expression strings that reference named constants — the device evaluates these at render time.
A minimal template with a background fill and repeating horizontal lines:
{
"name": "My Template",
"author": "Custom",
"orientation": "portrait",
"constants": [
{ "foreground": "#000000" },
{ "background": "#ffffff" },
{ "mobileMaxWidth": 1000 },
{ "offsetY": 100 }
],
"items": [
{
"id": "bg",
"type": "group",
"boundingBox": { "x": 0, "y": 0, "width": "templateWidth", "height": "templateHeight" },
"repeat": { "rows": "infinite", "columns": "infinite" },
"children": [
{
"type": "path",
"strokeColor": "background",
"fillColor": "background",
"antialiasing": false,
"data": ["M", 0, 0, "L", "parentWidth", 0, "L", "parentWidth", "parentHeight", "L", 0, "parentHeight", "Z"]
}
]
},
{
"type": "group",
"boundingBox": { "x": 0, "y": "offsetY", "width": "templateWidth", "height": 50 },
"repeat": { "rows": "down" },
"children": [
{
"type": "path",
"data": ["M", 0, 0, "L", "templateWidth", 0],
"strokeColor": "foreground",
"strokeWidth": 1
}
]
}
]
}| Field | Type | Description |
|---|---|---|
name |
string | Display name in the template picker |
author |
string | Author attribution |
orientation |
"portrait" | "landscape" |
Page orientation |
constants |
{key: value}[] |
Named constants, evaluated in order |
items |
TemplateItem[] |
Tree of groups, paths, and text items |
Templates use three item types, forming a tree via groups:
- Group — a bounding box with repeat rules and children. Groups tile their children across the page.
- Path — SVG-like drawing commands (
M,L,C,Z, etc.) with stroke/fill properties. - Text — positioned text with font size, alignment, and color.
Constants are a {key: value}[] array evaluated in declaration order — later entries may reference earlier ones. Device builtins are injected before user constants:
templateWidth,templateHeight— page dimensions in pixelspaperOriginX,paperOriginY— coordinate offsets
Expressions support arithmetic (+, -, *, /), comparisons (>, <, >=, <=, ==, !=), logical operators (&&, ||), and ternary (a > b ? x : y). Evaluation uses Function() after identifier substitution.
| Device | Portrait W×H | Landscape W×H | paperOriginX (portrait) |
|---|---|---|---|
| rm (RM 1 & 2) | 1404×1872 | 1872×1404 | −234 |
| rmPP (Paper Pro) | 1620×2160 | 2160×1620 | −270 |
| rmPPM (Paper Pro Move) | 814×1454 | 1454×814 | −320 |
Note: The PPM's physical panel is 954×1696 pixels, but xochitl injects
templateWidth=814andtemplateHeight=1454for template rendering. This is the only known device where template dimensions differ from physical panel resolution. Confirmed on-device with fw 3.26.0.68.
paperOriginX = templateWidth/2 − templateHeight/2
When a notebook page is created on a reMarkable device, it is stamped with that device's screen dimensions (templateWidth × templateHeight). These dimensions are stored in the page data and persist permanently — even when the notebook syncs to a different device model via the cloud.
This means a page created on a Paper Pro (1620×2160) will render at Paper Pro dimensions when viewed on a Paper Pro Move (814×1454), and vice versa. The template is not re-rendered at the viewing device's native resolution. This is normal device behavior, not a bug.
Design tip: Templates that use expression-based scaling (see below) adapt naturally across devices because the expressions are evaluated at page creation time using the creating device's constants. If you want a template to look good regardless of which device creates the page, use relative expressions rather than hardcoded pixel values.
Templates that need to adapt layout across devices can use either approach:
- Ternary branching —
"templateWidth > mobileMaxWidth ? bigValue : smallValue"(common in official templates) - Scale factors —
{ "scaleX": "templateWidth / 1404" }, then{ "margin": "scaleX * 60" }etc. (proportional scaling, fits all devices without branching)
Groups use repeat to tile their children across the page:
| Value | Behaviour |
|---|---|
0 |
Render once at tile origin (no repeat) |
N |
Render exactly N tiles |
"down" |
Fill downward from tile origin to viewport bottom |
"up" |
Fill upward from tile origin to viewport top |
"right" |
Fill rightward from tile origin to viewport right edge |
"infinite" |
Fill in both directions to cover the full viewport |
"expr" |
Any constant expression resolving to a number (e.g. "columns") |
The foreground and background constants are sentinel values recognized by the editor:
| Constant | Default | Role |
|---|---|---|
foreground |
#000000 |
Default stroke color; referenced by path items to stay invertible |
background |
#ffffff |
Canvas fill color; referenced by the bg item |
The Invert button swaps their values, letting you preview any color scheme. The bg item (full-page filled rectangle referencing background) renders the canvas background color on the device. Omit both if your template is always light-on-white with no color customization needed.
When you create or fork a template in the web app, the editor automatically:
- Replaces any path
strokeColor: "#000000"with theforegroundsentinel - Injects
strokeColor: "foreground"on paths with nostrokeColordefined (the device renders undefined stroke as black) - Replaces any path
fillColor: "#000000"withforeground(paths with nofillColorare left alone — undefined fill = transparent) - Appends
foreground/backgroundconstants if absent, and prepends thebgitem if absent
The result is a fully invertible template: hitting Invert swaps foreground ↔ background throughout.
The easiest way is through the web UI: click New template to create one from scratch, or select any template and click Save as New Template to fork it. The server handles file writes and registry updates automatically.
To add a template by hand (advanced):
- Place the
.templateJSON file inpublic/templates/custom/ - Add an entry to
public/templates/custom/custom-registry.jsonwith"isCustom": trueand a"custom/"filename prefix - Restart the dev server to pick up the new file