Working in Plangku
Security
What theme code can and can't do: no JavaScript, the allowed HTML, CSS restrictions, render limits and who may edit theme code.
Theme code is powerful, but it is deliberately limited. A theme is HTML, CSS and Plang, and it can't run JavaScript. This keeps customer websites fast and safe, and it lets the editor show your theme's output right inside the app without risk.
Why these rules exist:
- Customer sites run without JavaScript. Plangku's websites are plain server-rendered pages. There is nothing for a script to hook into.
- The editor shows theme output inline. The canvas draws your section's HTML on the app's own origin, next to the owner's logged-in session. A script there could act as the owner. So the output is cleaned before it is shown.
Where output is cleaned
Everything a theme renders is cleaned after the template runs:
- On the server for visitors (the live site and previews), with a sanitizer using the allowlist below.
- In the browser for the editor canvas, with the same rules applied to an inert document (nothing in it loads or runs while it is being cleaned).
Both use one allowlist (src/lib/code/allowlist.ts), so the editor and the live site keep exactly the same markup. Cleaning always runs, whatever your template does. Escaping in Plang is for correct text; this step is the safety boundary.
What is removed
| What | What happens |
|---|---|
<script>, <style>, <link>, <meta>, <base>, <template>, <noscript> | Removed together with their content. Use {% style %} for CSS |
<form>, <select>, <option>, <textarea>, <dialog> | Removed together with their content |
<object>, <embed>, <frame>, <frameset>, <applet>, <math>, <head> | Removed together with their content |
Any other tag that isn't on the allowed list (<marquee>, custom elements...) | The tag is removed, its text stays |
onclick, onerror and every other on... attribute | Removed |
javascript: and data: addresses in href, src and similar | Removed (the link stays, without an address) |
| HTML comments | Removed |
Forms are not available in themes because they need the platform's server features (spam protection, the inbox). Use the platform's own Form section for contact forms. Theme code can't send data anywhere.
Allowed elements
Text and layout: section, div, span, p, h1 to h6, a, ul, ol, li, strong, em, b, i, u, s, small, br, wbr, hr, blockquote, q, figure, figcaption, header, footer, article, aside, nav, address, time, mark, code, pre, kbd, sup, sub, cite, abbr, del, ins, bdi, data, dl, dt, dd.
Tables: table, thead, tbody, tfoot, tr, th, td, caption, colgroup, col.
Disclosure: details, summary.
Media: img, picture, source, video, iframe (see below).
Controls: button, label, input.
SVG: svg, path, circle, rect, line, polyline, polygon, ellipse, g, defs, linearGradient, radialGradient, stop, title, clipPath.
Notes:
- Buttons are always
type="button". A button can't submit anything. It is useful withpopovertargetor as a styled element. - Inputs are limited to
type="checkbox"andtype="radio", for CSS-only tricks with:checkedand alabel for. An input without a type is removed. iframeneeds anhttps://address on one of these hosts:www.youtube-nocookie.com,www.youtube.com,youtube.com,player.vimeo.com,maps.google.comandwww.google.com. Other iframes, and iframes without asrc, are removed.- Links with
target="_blank"getrel="noopener"added to whateverrelyou wrote.
Allowed attributes
On every element: class, style, id, title, role, lang, dir, hidden, tabindex, popover, plus any aria-* and data-*.
| Element | Extra attributes |
|---|---|
a | href, target (_blank or _self), rel, hreflang |
img | src, srcset, sizes, alt, width, height, loading, decoding, fetchpriority |
source | src, srcset, sizes, type, media |
video | src, poster, autoplay, muted, loop, playsinline, controls, preload, width, height |
iframe | src, width, height, loading, allow, allowfullscreen, referrerpolicy |
button | type, popovertarget, popovertargetaction, disabled |
input | type, name, value, checked, disabled |
label | for |
details | open, name |
ol, li | start, reversed, type; value |
time, data | datetime; value |
td, th, col, colgroup | colspan, rowspan, scope, span |
| SVG elements | The usual drawing attributes: viewBox, fill, stroke, stroke-width, d, cx, cy, r, x, y, points, transform, opacity, gradient attributes, width, height... |
Any other attribute is dropped (foo="bar", onclick, formaction...). tabindex must be a one or two digit number. Attribute values are cut at 5000 characters.
popover, hidden, open, checked, disabled) are kept as written, on the live site and in the editor alike.Addresses
href, src and poster accept https:// and http:// addresses, mailto: and tel: links (for href only), paths on the same website (/produk), #anchors and ?query links. Anything with another scheme (javascript:, data:, file:...) is removed. Images and video may use http:// and https:// addresses; for photos in templates prefer the image snippet, which only accepts uploaded images and https:// addresses.
Inline styles
A style="..." attribute is kept, but cleaned: expression(...), javascript: and behavior: are removed, any url(...) that isn't https://, /uploads/ or data:image/ becomes none, and the whole attribute is cut at 4000 characters.
CSS restrictions
Theme CSS is scoped, and some features are removed: @import, any at-rule other than @media, @container, @supports, @keyframes, @font-face and @layer, url() with anything but https://, /uploads/ and data:image/, behavior, -moz-binding, expression() and javascript:. The full rules and the editor warnings are on the styling page.
Limits
Render limits stop a template that loops too much or writes too much, so a theme can't hang a website: 200,000 steps and 1,500,000 characters of output per section, snippets nested at most 8 deep, ranges of at most 2000 numbers, and lists of at most 5000 items from split-style filters. A theme as a whole is limited to 150 files, 80 sections, 120 snippets, 12 CSS files, 60 images and 1.5 million characters. See language.
A section that breaks is shown as an error box in the editor and is simply empty for visitors. Saving is refused while any file has a syntax error.
Who can edit theme code
Theme code is edited in the Code tab, and only people with the Theme code permission can use it. The permission lets a team member edit the theme's code (HTML, CSS and section settings), upload a theme package and use the code assistant. It builds on the Edit pages & design permission, so ticking Theme code also ticks that one. Changes stay in draft until someone with the Publish permission publishes them.
- The website's owner can do everything.
- A team member has exactly the permissions the owner (or a member with the Team permission) gave them. See packages and history for uploading themes and going back to an earlier version.
- Client panel users can't edit code. A client panel is a white-label editor on the customer's own domain, for clients who should change their text and photos but not the site's structure. Panel users can have the permissions edit, publish, data, inbox, SEO, tracking, stats and settings, and the panel has no Code tab. Only the owner and ordinary team members can hold the Theme code permission; clients work with the settings of the sections you build.
This is the reason to put everything a client may want to change into section settings ({% schema %}), such as texts, photos, colours and links: clients can edit those from the visual editor without ever touching code. See sections and settings.
A short checklist
- Don't try to add behaviour with scripts. Use
<details>, thepopoverattribute,:target,:checkedand CSS scroll snapping instead (see examples). - Use
type="button"buttons only withpopovertarget. - For videos use
youtube_idand an iframe on an allowed host. - Use
https://for every outside image, font and link. - Put every setting a client might change in
{% schema %}.