Working in Plangku
Theme packages
Build a theme as a .zip outside Plangku and upload it, with its settings, images and starting pages.
What a package is
A theme package is a .zip file that holds a theme. Agencies use it to build a theme in their own editor, keep it in Git, and install it on client websites.
The way in is one-way. You can upload a package, but you cannot download a theme as a package. Themes are edited and uploaded here, and they only run on Plangku's engine.
To upload one, open the code editor and click the cloud icon (Upload a theme package) in the Explorer or the left bar. You need the Theme code permission.
The layout
theme.json optional
config/settings_schema.json optional, theme settings
config/settings_data.json optional, a starting design (mode "all")
assets/*.css CSS
assets/*.png, .jpg, .webp ... images
sections/*.plang sections
snippets/*.plang snippets
templates/index.json optional, the home page (mode "all")
templates/<address>.json optional, other pages (mode "all")The files in sections/, snippets/, assets/, config/settings_schema.json and theme.json follow the same rules as in the editor. See Files and structure.
You can zip the files at the top level, or inside one folder (my-theme/sections/...). A single wrapping folder is removed. Folders and files named __MACOSX, .DS_Store, Thumbs.db and .git are ignored. Any other file that is not part of a theme is skipped, and the result lists it as "left out".
All text files must be UTF-8.
Images
Put images in assets/. Allowed types are .png, .jpg, .jpeg, .webp, .gif and .avif. Plangku uploads each one the same way as a photo added in the editor, and it must pass the usual image checks.
Use an image in three places:
- In a schema default or preset:
"default": "assets/banner.jpg". - In
settings_data.jsonandtemplates/*.json:"photo": "assets/banner.jpg". - In a template:
{{ 'banner.jpg' | asset_url }}.
In the first two, assets/<file> becomes the uploaded image's address. The images count towards the 60-image limit.
Pages: templates/*.json
A page file describes one page.
{
"title": "Beranda",
"sections": [
{ "type": "hero", "settings": { "heading": "Kopi dari kebun sendiri" } },
{ "type": "promo", "settings": { "heading": "Promo hari ini" }, "hidden": false }
],
"seo": { "title": "Warung Kopi Senja", "description": "Kopi Jawa, diantar ke rumah." }
}templates/index.jsonis the home page.templates/tentang.jsonbecomes the page at/tentang. The address is lowercase letters, numbers and-, up to 100 characters.typeis a built-in section type, such asheroorcta, or the handle of one of your theme's sections. Header and footer cannot be used, and a type that is neither is dropped.- A page can have up to 60 sections.
titledefaults to the address with spaces (or "Beranda" for the home page).hidden: truehides the section.
type that is neither a built-in section nor one of the package's sections is left out, and the upload result lists it.A website can have up to 20 pages. If a package would add a page over that, the new page is skipped, and the upload result says which one.
config/settings_data.json
An optional starting design. Each part is optional.
{
"theme": {
"colors": { "brand": "#B45309", "accent": "#F59E0B" },
"headingFont": "Poppins",
"bodyFont": "Inter",
"custom": { "promo_color": "#B45309" }
},
"header": { "settings": { "logoText": "Warung Kopi Senja" } }
}themeis merged into the website's theme. Colours are merged one by one, so you can set onlybrand.customholds the values of your theme settings.headerandfooterare merged into the existing header and footer settings. You can write the settings directly ("header": { "logoText": "..." }) or under"settings".
The two upload modes
When you upload, you choose what the package replaces.
| Theme code only | Theme code, design and pages | |
|---|---|---|
| Mode | code | all |
| Theme files | Replaced: every theme file is replaced by the package's | Replaced, the same way |
| Colours, fonts, header, footer | Stay | Merged from settings_data.json, if present |
| Pages | Stay | templates/*.json pages are added. A page with the same address is replaced. Other pages are kept |
| Texts and photos in other pages | Stay | Stay |
"Replaced" means the website's theme files become exactly the package's files. A file that is in the website but not in the package is deleted. So the package must contain every file you want to keep, including theme.json and config/settings_schema.json.
Everything lands in the draft. Nothing is live until you publish.
Limits
| Limit | Value |
|---|---|
Size of the .zip | 11 MB |
Files in the .zip | 400 |
| One file, unpacked | 8 MB |
| All files, unpacked | 40 MB |
The theme's own limits also apply (150 files, 120,000 characters per text file and so on). See Files and structure.
The .zip must use standard compression (deflate or stored). Password-protected archives and ZIP64 are not supported. File names with .., backslashes or absolute paths are refused.
Errors and the backup
Plangku checks the theme's code before it changes anything. If any file has an error, the upload stops. Nothing is installed, no image is uploaded, and the dialog lists the first errors with file and line.
If the code is fine, Plangku saves a backup first. It is a website version named "Before a theme upload", and you can find it in History, under Whole theme. Restoring it brings back the old theme files. Then it installs the package.
When it finishes, the dialog shows how many sections, snippets, files, images and pages were installed. Check the preview, then publish.
A complete small example
This package has a promo section, a snippet, a theme setting, a stylesheet, a starting design, an image and two pages.
warung-kopi/
theme.json
config/
settings_schema.json
settings_data.json
assets/
site.css
banner.jpg
sections/
promo.plang
snippets/
price-tag.plang
templates/
index.json
promo.jsontheme.json
{ "name": "Warung Kopi", "version": "1.0.0", "author": "Studio Kopi Jawa" }config/settings_schema.json
[
{
"name": "Colours",
"settings": [
{ "type": "color", "id": "promo_color", "label": "Promo colour", "default": "#B45309" }
]
}
]config/settings_data.json
{
"theme": {
"colors": { "brand": "#B45309", "accent": "#F59E0B" },
"headingFont": "Poppins",
"bodyFont": "Inter",
"custom": { "promo_color": "#B45309" }
},
"header": { "settings": { "logoText": "Warung Kopi Senja", "ctaLabel": "Pesan lewat WhatsApp" } }
}assets/site.css
.price-tag { margin-top: 1rem; font-size: 1.5rem; }
.price-tag strong { color: var(--ts-promo-color); }
.price-tag s { margin-left: .5rem; font-size: 1rem; color: var(--s-muted); }assets/banner.jpg is a normal photo file (not shown here).
snippets/price-tag.plang
<p class="price-tag">
<strong>{{ price | money }}</strong>
{%- if old_price > 0 %} <s>{{ old_price | money }}</s>{% endif %}
</p>sections/promo.plang
{%- assign s = section.settings -%}
<div class="site-wrap promo">
<div>
{% render 'heading', heading: s.heading, text: s.text %}
{% render 'price-tag', price: s.price, old_price: s.old_price %}
<p class="promo-action">{% render 'button', label: s.button_label, link: s.button_link, size: 'lg' %}</p>
</div>
{% render 'image', src: s.photo, alt: s.heading, class: 'promo-photo' %}
</div>
{% style %}
.promo { display: grid; gap: 2rem; align-items: center; }
.promo-photo { width: 100%; aspect-ratio: 4 / 3; border-radius: var(--r); }
.promo-action { margin-top: 1.5rem; }
@container (min-width: 48rem) {
.promo { grid-template-columns: 1fr 1fr; }
}
{% endstyle %}
{% schema %}
{
"name": "Promo kopi",
"description": "A promo with a price, a photo and a WhatsApp button",
"icon": "Coffee",
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "Paket Kopi Hemat" },
{ "type": "textarea", "id": "text", "label": "Text", "default": "Tiga bungkus biji kopi pilihan, gratis ongkir." },
{ "type": "number", "id": "price", "label": "Price (Rp)", "min": 0, "step": 1000, "default": 85000 },
{ "type": "number", "id": "old_price", "label": "Old price (Rp, 0 to hide)", "min": 0, "step": 1000, "default": 105000 },
{ "type": "image", "id": "photo", "label": "Photo", "default": "assets/banner.jpg" },
{ "type": "text", "id": "button_label", "label": "Button label", "default": "Pesan lewat WhatsApp" },
{ "type": "link", "id": "button_link", "label": "Button link", "default": "wa:Halo, saya mau pesan paket kopi hemat" }
],
"presets": [{ "name": "Promo kopi" }]
}
{% endschema %}templates/index.json
{
"title": "Beranda",
"sections": [
{ "type": "hero", "settings": { "heading": "Kopi dari kebun sendiri", "text": "Disangrai setiap pagi, diantar ke rumah Anda." } },
{
"type": "promo",
"settings": {
"heading": "Paket Kopi Hemat",
"text": "Tiga bungkus biji kopi pilihan, gratis ongkir.",
"price": 85000,
"old_price": 105000,
"photo": "assets/banner.jpg",
"button_label": "Pesan lewat WhatsApp",
"button_link": "wa:Halo, saya mau pesan paket kopi hemat"
}
}
],
"seo": { "title": "Warung Kopi Senja", "description": "Kopi Jawa, diantar ke rumah." }
}templates/promo.json
{
"title": "Promo",
"sections": [
{
"type": "promo",
"settings": {
"heading": "Promo bulan ini",
"text": "Stok terbatas.",
"price": 85000,
"old_price": 0,
"photo": "assets/banner.jpg",
"button_label": "Pesan lewat WhatsApp",
"button_link": "wa:Halo, saya mau pesan"
}
}
]
}Zip the warung-kopi folder, upload it with Theme code, design and pages, and the home page and /promo appear in the draft.