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

WhatWhat 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... attributeRemoved
javascript: and data: addresses in href, src and similarRemoved (the link stays, without an address)
HTML commentsRemoved

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 with popovertarget or as a styled element.
  • Inputs are limited to type="checkbox" and type="radio", for CSS-only tricks with :checked and a label for. An input without a type is removed.
  • iframe needs an https:// address on one of these hosts: www.youtube-nocookie.com, www.youtube.com, youtube.com, player.vimeo.com, maps.google.com and www.google.com. Other iframes, and iframes without a src, are removed.
  • Links with target="_blank" get rel="noopener" added to whatever rel you wrote.

Allowed attributes

On every element: class, style, id, title, role, lang, dir, hidden, tabindex, popover, plus any aria-* and data-*.

ElementExtra attributes
ahref, target (_blank or _self), rel, hreflang
imgsrc, srcset, sizes, alt, width, height, loading, decoding, fetchpriority
sourcesrc, srcset, sizes, type, media
videosrc, poster, autoplay, muted, loop, playsinline, controls, preload, width, height
iframesrc, width, height, loading, allow, allowfullscreen, referrerpolicy
buttontype, popovertarget, popovertargetaction, disabled
inputtype, name, value, checked, disabled
labelfor
detailsopen, name
ol, listart, reversed, type; value
time, datadatetime; value
td, th, col, colgroupcolspan, rowspan, scope, span
SVG elementsThe 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.

Note: attributes without a value (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

  1. Don't try to add behaviour with scripts. Use <details>, the popover attribute, :target, :checked and CSS scroll snapping instead (see examples).
  2. Use type="button" buttons only with popovertarget.
  3. For videos use youtube_id and an iframe on an allowed host.
  4. Use https:// for every outside image, font and link.
  5. Put every setting a client might change in {% schema %}.