PATCH /v1/themes/:id, POST /v1/themes/validate) and a refused document is a THEME_INVALID error with a list of problems:
path is dotted: blocks.2.html, blocks.4.columns.0.1.alt, settings.button_label, custom_css. CSS problems start with Line N:.
At render time the same checks protect the page again, and a theme that cannot be parsed falls back to the Tendr default look.
What themes can never do
- Select, cover, move or restyle the sealed payment block.
- Add any shopper input: no forms, inputs, buttons, selects, textareas or editable text.
- Load code, other pages, or files from outside Tendr.
- Show a wallet address, payment instructions or a payment timer of their own.
- Write words with CSS.
transform, large z-index values and similar tricks stay inside the block and cannot reach the payment block.
Custom CSS
Maximum 50 KB. Your rules are scoped to your blocks, so selectors need no prefix.Selectors
* is fine as part of a longer selector, such as .card *.
At-rules
Only@media, @supports and @keyframes are allowed. Everything else, including @import and @font-face, is refused with `@import` isn't allowed — only `@media`, `@supports` and `@keyframes` are. To use your own font, upload it and choose it in the theme settings.
@keyframes needs a plain name and frame selectors that are from, to or percentages. Tendr prefixes your animation names so they cannot replace its own.
Properties and values
CSS cannot draw text
An address can be assembled from short strings spread over several pseudo-elements or keyframes, and no pattern check can catch that reliably. So CSS may not draw any text at all. These properties accept only text-free values, and anything else is refused withCSS can't add text — put words in a text block instead:
content:none,normal,"", the quote keywords (open-quote,close-quote,no-open-quote,no-close-quote), onecounter(name)andurl()of an uploaded image. Notattr(),counters(),var(),env(),string(),symbols()or any non-empty string.quotes:noneorauto.list-style,list-style-type,text-emphasis,text-emphasis-style,text-overflowandhyphenate-character: keywords only.var()in any of these properties.
counter()andlist-style-typeacceptdecimal,decimal-leading-zero,disc,circle,squareornone. Anything else:Counters and list markers can only use `decimal`, `decimal-leading-zero`, `disc`, `circle`, `square` or `none`.contentcan show at most onecounter():`content` can show at most one `counter()`.counter-reset,counter-setandcounter-incrementtake whole numbers from -999 to 999:Counter values must be whole numbers from -999 to 999.
Custom HTML
Thecustom_html block takes up to 20 KB (HTML block is over 20 KB). Tendr sanitises it on save and stores the cleaned HTML.
Allowed tags: p br span div strong em b i u s small h2 h3 h4 ul ol li blockquote a img figure figcaption hr table thead tbody tr th td. Allowed attributes: class and style on any tag, href, rel and target on a, src and alt on img. Other tags are dropped, and these are refused outright:
Links get
rel="noopener noreferrer" and target="_blank" automatically.
Text rules
These apply to every text a theme carries: headings, rich text, banners, alt text, FAQ, reviews, delivery notes, footer link labels, countdown labels, the display name, the success message and the button label. Address-like text. Refused withText can't contain wallet addresses or payment instructions:
- EVM addresses:
0xfollowed by 40 hex characters. - Bitcoin bech32:
bc1…ortb1…. - Base58 strings of 25 to 44 characters that mix letters and digits.
- 64 hex characters (NEAR implicit accounts).
- NEAR names ending in
.near,.testnetor.tg.
https://… part of [text](https://…)) are not checked for base58 strings, because they are not shown and URL paths often look like one; the link text is checked like any other text.
Invisible and format characters are stripped before checking, and in HTML the checks run on the visible text of the whole block, so splitting an address across tags does not hide it.
Payment instructions. A phrase like “send … to the address”, “transfer … to wallet”, “deposit … to account 0x” or “pay … to bc1” is refused with the same message.
Countdown labels. A countdown label cannot look like the payment timer: no expire, expires, expiring, expiry, session, deadline, time left, pay within or left to pay. The message is Countdown text can't look like the payment timer (avoid words like expires, session, deadline, time left).
Button words. settings.button_label follows these rules:
The message names the word that was refused. Everyday words that are also codes, such as Try, all, top or cup, are fine unless written in capitals.
{amount} is filled in by Tendr with the real amount. The label applies to the main Pay button only.
Rich text. rich_text cannot contain HTML (Text can't contain HTML), inline images (Text can't contain inline images), reference-style links (Reference-style links are not supported), or links that do not start with https:// (Links in text must start with https://).
Other document rules.
Risks Tendr accepts
Pattern checks on text are best effort. These cases cannot be fully detected:- Address-like text split by hidden elements or punctuation. Pieces of an address separated by invisible spans (
display: none, zero font size) or by punctuation can read as an address to a shopper without matching a pattern. - An image showing a fake QR code or address. Tendr can validate that a file is an image, not what the picture shows.
- A video showing a fake QR code or address. Like images, an embedded video can show anything; Tendr does not inspect what it shows.

