Di 23 Templates kita kenal semua tipe template dan strukturnya. Sekarang kita bedah isinya satu-satu: object apa yang dipakai, form apa yang wajib, dan jebakan di tiap halaman.
Satu prinsip yang berlaku buat semua template di bab ini: kalau template-nya JSON, semua HTML dan Liquid harus ada di dalam section yang dirujuk template. Jadi waktu bab ini bilang “template produk harus punya form produk”, maksudnya section utama template produk (biasanya main-product.liquid) yang punya form-nya.
Tiap tipe punya contoh implementasi di theme Dawn yang bisa kamu buka di repository Dawn: template-nya di templates/, section utamanya di sections/main-*.liquid.
index
Halaman depan di URL root /. Ini kesan pertama pelanggan, jadi template-nya harus ngasih opsi yang luwes buat merchant nyeritain tokonya: banner, produk unggulan, koleksi, testimoni, dan sebagainya.
Bawaannya hampir gak ada isi “wajib” di sini, karena semua tergantung section yang dipilih merchant. Yang penting: pakai index.json, bukan index.liquid. Dua-duanya bikin merchant bisa ngatur section, tapi JSON lebih luwes (bisa app section, bisa diurutin bebas) dan nyimpen datanya sendiri sehingga theme editor lebih cepat.
Kalau kamu nemuin index.liquid di theme lama, isinya pasti ada {{ content_for_index }} yang dibahas di 13 Objects Global > 3. content_for_index. Di index.json, object itu gak dibutuhin.
product
Halaman produk: media, konten, dan form buat milih varian dan nambah ke keranjang. Ini template paling penting di toko, karena di sinilah keputusan beli terjadi.
Yang harus ada di template (atau section utamanya):
- Object
productdari 14 Objects Produk dan Koleksi > product buat nampilin detailnya. - Form produk dengan komponen: pemilih varian, input kuantitas, tombol accelerated checkout, dan input properti baris (opsional).
- Opsional: rekomendasi produk.
1. Form Produk
Cara utama pelanggan nambah varian ke keranjang. Pakai tag form bertipe product dari 12 Tags Khusus Theme > 1. product:
{% form 'product' %}
<!-- form content -->
<input type="submit" value="Add to cart" />
{% endform %}Kalau butuh atribut kustom di <form>-nya (class, ID), tambahin sebagai parameter tag.
2. Pemilih Varian
Biasanya berupa satu atau lebih input nilai opsi (dropdown Size, tombol Color) yang nentuin varian mana yang dibeli. Yang dikirim ke server tetap <input name="id"> berisi ID varian, jadi pemilih opsi itu cuma antarmuka yang nyari varian yang cocok terus ngisi input id-nya. Implementasi lengkapnya di 39 Produk Variants dan High-variant Products.
3. Input Kuantitas
Wajib punya atribut name="quantity" dengan nilai bilangan bulat minimal 1:
<input type="text" name="quantity" min="1" value="1" />4. Tombol Accelerated Checkout
Biar pelanggan bisa langsung beli tanpa lewat keranjang (Buy it now, Shop Pay, PayPal). Pakai filter payment_button dari 20 Filters Money, Metafield, Cart, Customer, dan Localization > 1. payment_button, di dalam form:
{% form 'product' %}
<!-- form content -->
<input type="submit" value="Add to cart" />
{{ form | payment_button }}
{% endform %}Bahasan gaya dan kustomisasinya di 43 Accelerated Checkout.
5. Properti Baris
Buat ngasih pelanggan opsi nambahin informasi tambahan ke item yang dibeli: teks ukiran, monogram, atau upload file. Ditangkap lewat input name="properties[nama-properti]", wajib di dalam form produk:
{% form 'product' %}
<!-- form content -->
<input type="text" name="properties[Monogram]" />
<input type="submit" value="Add to cart" />
{% endform %}Cara nampilinnya di keranjang ada di bagian cart di bawah.
❌ salah, input properti di luar form, nilainya gak ikut terkirim
<input type="text" name="properties[Monogram]" />
{% form 'product' %}
<input type="submit" value="Add to cart" />
{% endform %}✅ benar
{% form 'product' %}
<input type="text" name="properties[Monogram]" />
<input type="submit" value="Add to cart" />
{% endform %}6. Cart AJAX API dan Rekomendasi
Dua hal yang bikin halaman produk modern:
- Cart API (bagian dari Ajax API) buat nambah varian ke keranjang tanpa pindah ke halaman keranjang. Bahasannya di 51 Ajax API dan Section Rendering API.
- Product Recommendations API buat nampilin produk terkait dan pelengkap. Bahasannya di 41 Produk Recommendations, Gift Card, dan Bundles.
7. Template Seller Product
Ada satu alternate template produk khusus: product.remote.seller.json, buat nampilin produk dari toko lain (fitur remote products). Di theme editor namanya Seller product.
Aturannya:
- Semua produk remote otomatis pakai template ini, dan toko gak bisa ngasih template ini ke produknya sendiri.
- Awalan
remote.itu dilindungi, gak boleh dipakai buat tujuan lain. - Kalau theme pakai Liquid template, namanya
product.remote.seller.liquid. - Template ini bisa disediain theme dari awal, atau toko diminta bikin waktu pasang app yang butuh. Kalau toko gak ikut fitur remote products, template ini gak muncul di theme editor walaupun ada di kode.
Isinya harus cuma info inti produk: nama, harga, media, deskripsi, pemilih varian, kuantitas, tombol beli. Yang jangan dimasukin: nilai brand (“Berkelanjutan”, “Fair Trade”), info pengiriman dan refund, opsi pembayaran khusus (Klarna, Afterpay), bundle atau diskon, dan apapun yang bergantung pada metafield, karena gak ada jaminan produk dari toko lain punya metafield yang sama. Object remote_product-nya dibahas di 14 Objects Produk dan Koleksi > Object Remote.
collection
Halaman koleksi yang ngelist semua produk di dalamnya. Yang harus ada: object collection dari 14 Objects Produk dan Koleksi > collection.
Tiga hal yang harus dikuasai di template ini:
1. Filter
Pakai storefront filtering buat nyaring koleksi jadi subset yang lebih kecil. Object filter-nya di 17 Objects Lainnya > Object Filtering, implementasinya di 37 Filtering.
2. Urutan
Urutan produk diatur lewat parameter URL sort_by:
https://my-store.myshopify.com/collections/frontpage?sort_by=price-descendingDari object collection kamu dapat sort_options (semua pilihan), sort_by (yang aktif), dan default_sort_by (bawaan). Polanya: tampilin di <select>, terus pakai JavaScript buat nambahin parameter URL dan refresh:
<select id="sort-by">
{% assign sort_by = collection.sort_by | default: collection.default_sort_by %}
{% for option in collection.sort_options %}
<option value="{{ option.value }}" {% if option.value == sort_by %}selected="selected"{% endif %}>
{{ option.name }}
</option>
{% endfor %}
</select>
<script>
Shopify.queryParams = {};
// Preserve existing query parameters
if (location.search.length) {
var params = location.search.substr(1).split('&');
for (var i = 0; i < params.length; i++) {
var keyValue = params[i].split('=');
if (keyValue.length) {
Shopify.queryParams[decodeURIComponent(keyValue[0])] = decodeURIComponent(keyValue[1]);
}
}
}
// Update sort_by query parameter on select change
document.querySelector('#sort-by').addEventListener('change', function(e) {
var value = e.target.value;
Shopify.queryParams.sort_by = value;
location.search = new URLSearchParams(Shopify.queryParams).toString();
});
</script>Perhatiin Shopify.queryParams yang nyimpen parameter yang udah ada (misalnya filter yang aktif) sebelum nambahin sort_by. Kalau langsung location.search = '?sort_by=...', filter yang aktif bakal hilang.
3. Paginasi
collection.products maksimal 50 per halaman, jadi wajib di-paginate biar semua produk bisa diakses:
{% paginate collection.products by 20 %}
{% for product in collection.products %}
<!-- product info -->
{% endfor %}
{{ paginate | default_pagination }}
{% endpaginate %}Aturannya udah dibahas di 11 Tags Perulangan > paginate.
4. Produk Remote
Kalau toko ikut fitur remote products, produk dari toko lain otomatis muncul di koleksi tanpa perubahan theme, dengan lencana khusus di gambarnya. Syaratnya theme harus pakai collection.products dari Liquid, bukan API.
list-collections
Halaman daftar semua koleksi di /collections. Yang harus ada: object collections dari 13 Objects Global > 2. collections.
1. Urutan Koleksi
Loop biasa ngasih urutan abjad:
{% for collection in collections %}
<!-- collection info -->
{% endfor %}Kalau merchant mau urutan sendiri, caranya: bikin menu berisi koleksi-koleksi sesuai urutan, sediain setting bertipe link_list biar merchant milih menunya, terus loop item menu yang tipenya collection_link dan ambil koleksinya dari link.object:
{% for link in settings.collection_list_menu.links %}
{% if link.type == 'collection_link' %}
{% assign collection = link.object %}
<!-- collection info -->
{% endif %}
{% endfor %}Ini contoh bagus pemakaian link.object dan link.type dari 15 Objects Konten > 2. link.
2. Gambar Cadangan
Koleksi gak selalu punya gambar. Sediain cadangan, misalnya gambar produk pertama di koleksi:
{% if collection.image %}
{{ collection.image | image_url: width: 450, height: 450 | image_tag: collection.image.alt }}
{% else %}
{% assign alt = collection.title | escape %}
{{ collection.products.first.image | image_url: width: 450, height: 450 | image_tag: alt }}
{% endif %}cart
Halaman /cart dengan ringkasan isi keranjang, biasanya tabel dengan satu baris per line item. Yang harus ada: object cart dari 16 Objects Cart, Checkout, Order, dan Customer > cart.
Ingat, satu line item = satu varian. Kalau pelanggan nambah kaos ukuran M dan L, itu dua baris.
1. Loop Item
{% for item in cart.items %}
<!-- line item info -->
{% endfor %}2. Lanjut ke Checkout
Item-item keranjang harus di dalam <form> dengan action="{{ routes.cart_url }}" dan method="post", plus input submit bernama checkout yang jadi pemicu ke halaman checkout:
<form action="{{ routes.cart_url }}" method="post">
{% for item in cart.items %}
<!-- line item info -->
{% endfor %}
<input type="submit" name="checkout" value="Checkout" />
</form>Atau pakai tag form bertipe cart dari 12 Tags Khusus Theme > 2. cart yang ngasilin form yang sama.
❌ salah, tombolnya gak bernama checkout, cuma reload halaman
<input type="submit" value="Checkout" />✅ benar
<input type="submit" name="checkout" value="Checkout" />3. Hapus Item
Sediain link dengan href ke item.url_to_remove:
{% for item in cart.items %}
<!-- line item info -->
<a href="{{ item.url_to_remove }}">Remove</a>
{% endfor %}Buat versi tanpa reload halaman, pakai endpoint /cart/change dari Cart API.
4. Update Kuantitas
Input dengan name="updates[]" dan nilai kuantitas sekarang, per item:
{% for item in cart.items %}
<!-- line item info -->
<input type="text" name="updates[]" value="{{ item.quantity }}" />
{% endfor %}Terus tombol submit biasa di dalam form keranjang buat nerapin:
<input type="submit" value="Update cart" />Submit form ini reload halaman dengan kuantitas baru. Buat tanpa reload, pakai endpoint /cart/update.
5. Diskon
Diskon bisa kena seluruh keranjang atau per item, jadi tampilin di dua tempat: di ringkasan total dan di tiap baris. Object-nya di 16 Objects Cart, Checkout, Order, dan Customer > Object Diskon, implementasinya di 42 Harga Diskon, Currency, Unit Pricing, Installments, B2B.
6. Catatan dan Atribut Keranjang
Buat ngasih pelanggan opsi info tambahan buat seluruh order. Perhatiin atribut form="cart" di input-nya: ini atribut HTML yang bikin input tetap terhubung ke form keranjang walaupun ditaruh di luar <form>-nya, misalnya di section footer keranjang yang terpisah dari section item.
Catatan keranjang, input bernama note:
<textarea name="note" form="cart"></textarea>Atribut keranjang, input bernama attributes[nama]:
<p>
<label>Please let us know your favorite color</label>
<input type="text" name="attributes[Favorite color]" form="cart" value="{{ cart.attributes['Favorite color'] }}" />
</p>Nilainya dibaca lewat cart.note dan cart.attributes, udah dibahas di 16 Objects Cart, Checkout, Order, dan Customer > 4. Catatan dan Atribut.
7. Tampilin Properti Baris
Properti yang ditangkap di form produk ditampilin dengan loop item.properties. Ada trik buat file yang di-upload: nilainya berisi /uploads/, jadi tampilin sebagai link:
{% for item in cart.items %}
<!-- line item info -->
{% unless item.properties == empty %}
{% for property in item.properties %}
{{ property.first }}:
{% if property.last contains '/uploads/' %}
<a href="{{ property.last }}">{{ property.last | split: '/' | last }}</a>
{% else %}
{{ property.last }}
{% endif %}
{% endfor %}
{% endunless %}
{% endfor %}Dan catatan: dua item yang sama tapi propertinya beda dipisah jadi dua baris. Kaos M dengan ukiran “A” dan kaos M dengan ukiran “B” itu dua line item.
8. Produk Remote
Produk dari toko lain bisa ada di keranjang tanpa perubahan theme. Judulnya bakal berisi nama toko asalnya, dan bisa dikenali lewat product.remote_details. Theme harus pakai cart.items atau Cart Ajax API, bukan Storefront API.
page
Halaman statis kaya About us dan Contact us. Yang harus ada: object page dari 15 Objects Konten > page, terutama page.title dan page.content.
Ini template paling sederhana. Yang biasanya ditambahin: alternate template page.contact.json yang punya section form kontak, dibahas di 46 Customer Sign-in, Account Component, Contact Form, Email Consent.
blog
Halaman blog yang ngelist semua artikelnya. Yang harus ada: section yang pakai object blog dari 15 Objects Konten > blog, dengan blog.articles yang di-paginate.
Filter Artikel per Tag
Pakai tag artikel buat nyaring blog, dengan nambahin /tagged/[handle-tag] ke URL blog:
https://my-store.myshopify.com/blogs/main/tagged/newsBeberapa tag digabung pakai +:
https://my-store.myshopify.com/blogs/main/tagged/news+breakingJangan bikin URL ini manual, pakai filter link_to_tag, link_to_add_tag, dan link_to_remove_tag dari 19 Filters HTML, URL, Media, Color, dan Font > 3. Filter Tag Filtering.
article
Halaman satu artikel dengan isi lengkap dan komentar opsional. Yang harus ada: object article dari 15 Objects Konten > article dan form komentar.
1. Form Komentar
Pakai tag form bertipe new_comment dengan object article sebagai parameter. Di dalamnya wajib tiga input dengan nama tertentu:
- Nama:
type="text",name="comment[author]". - Email:
type="email",name="comment[email]". - Komentar:
textarea,name="comment[body]".
{% form 'new_comment', article %}
{{ form.errors | default_errors }}
<div class="name">
<label for="name">Name</label>
<input type="text" name="comment[author]" value="{{ form.author }}" />
</div>
<div class="email">
<label for="email">Email</label>
<input type="email" name="comment[email]" value="{{ form.email }}" />
</div>
<div class="comment">
<label for="comment">Comment</label>
<textarea name="comment[body]">{{ form.body }}</textarea>
</div>
<div class="submit">
<input type="submit" value="Post" />
</div>
{% endform %}Perhatiin value="{{ form.author }}" dan seterusnya: itu isi ulang dari object form di 17 Objects Lainnya > form kalau submit gagal. Dan kasih umpan balik ke pelanggan: berhasil, atau errornya apa, pakai form.posted_successfully? dan form.errors.
2. Paginasi Komentar
article.comments maksimal 50 per halaman, jadi di-paginate:
{% paginate article.comments by 20 %}
{% for comment in article.comments %}
<!-- comment info -->
{% endfor %}
{{ paginate | default_pagination }}
{% endpaginate %}search
Halaman /search dengan hasil pencarian. Yang harus ada: object search dari 17 Objects Lainnya > search, form pencarian, dan hasil pencarian.
1. Form Pencarian
<form> dengan action="{{ routes.search_url }}" dan input type="text" bernama q. Isi value-nya dengan search.terms biar kata yang dicari tetap ada di kolom setelah hasil muncul:
<form action="{{ routes.search_url }}">
<input type="text"
placeholder="Search"
name="q"
value="{{ search.terms | escape }}"
/>
<input type="submit" value="Search" />
</form>Perhatiin | escape di value: kata pencarian itu input pengguna, wajib di-escape.
2. Hasil
Loop search.results, bedain tipenya pakai object_type:
{% for item in search.results %}
<!-- item details -->
{% endfor %}3. Sorot Kata Pencarian
Pakai filter highlight buat nebelin kata yang dicari di dalam konten hasil:
{% for item in search.results %}
<!-- item details -->
{{ item.content | highlight: search.terms }}
{% endfor %}Hasilnya <strong class="highlight"> yang bisa kamu gayain. Bahasan lengkap pencarian di 38 Search dan Predictive Search.
404
Halaman kalau pelanggan buka URL yang gak ada. Gak ada object yang disarankan, tapi template harus jelas bilang halamannya gak ketemu dan ngasih jalan keluar: link ke halaman populer, kolom pencarian, atau link ke beranda atau koleksi buat lanjut belanja:
<p>
404
</p>
<h1 class="title">
Page not found
</h1>
<a href="{{ routes.all_products_collection_url }}" class="button">
Continue shopping
</a>Perhatiin routes.all_products_collection_url, bukan /collections/all manual, sesuai aturan 13 Objects Global > routes.
password
Halaman /password waktu toko dikunci. Isinya pesan yang bisa diedit merchant dan form password, plus opsional form daftar email.
1. Pesan Password
Dari shop.password_message:
{% unless shop.password_message == blank %}
{{ shop.password_message }}
{% endunless %}2. Form Password
Tag form bertipe storefront_password dengan input type="password" bernama password:
{% form 'storefront_password' %}
{{ form.errors | default_errors }}
<div class="password">
<label for="password">Password</label>
<input type="password" name="password" />
</div>
<div class="submit">
<input type="submit" value="Sign in" />
</div>
{% endform %}3. Form Daftar Email
Buat ngumpulin email calon pelanggan sebelum toko buka. Tag form bertipe customer dengan input tersembunyi contact[tags] berisi prospect, password page dan input email contact[email]:
{% form 'customer' %}
{{ form.errors | default_errors }}
<div class="tags">
<input type="hidden" name="contact[tags]" value="prospect, password page" />
</div>
<div class="email">
<label for="email">Email</label>
<input type="email" name="contact[email]" />
</div>
<div class="submit">
<input type="submit" value="Sign in" />
</div>
{% endform %}Tag prospect dan password page itu yang Shopify pakai buat bantu merchant marketing ke pelanggan ini nanti, tapi kamu bisa pakai tag sendiri.
Catatan dari 04 Dev Store, Akun Kolaborator, dan Theme Access > 3. Bagiin Dev Store: di dev store, halaman password kustom ini gak dipakai buat ngontrol akses. Yang tampil selalu halaman password khusus dev store. Buat lihat versi kustomnya, login dulu terus buka /password.
gift_card.liquid
Halaman gift card yang dilihat pelanggan setelah beli. Wajib Liquid, gak bisa JSON. Dan beda dari halaman lain, ini di-host di domain checkout.shopify.com:
https://checkout.shopify.com/gift_cards/[store_id]/[gift_card_token]Yang harus ada: object gift_card dari 16 Objects Cart, Checkout, Order, dan Customer > gift_card.
1. QR Code
Tambahin skrip QR code bawaan Shopify di <head>:
{{ 'vendor/qrcode.js' | shopify_asset_url | script_tag }}Terus di <body>, bikin QR yang isinya bisa diatur lewat text:
<div id="qr-code"></div>
<script>
new QRCode(document.getElementById('qr-code'), {
text: '{{ shop.url }}',
width: 120,
height: 120
});
</script>Buat QR yang isinya kode gift card, pakai gift_card.qr_identifier.
2. Apple Wallet
{% if gift_card.pass_url %}
<a href="{{ gift_card.pass_url }}" >
<img id="apple-wallet-badge"
src="{{ 'gift-card/add-to-apple-wallet.svg' | shopify_asset_url }}"
width="120"
height="40"
alt="Add To Apple Wallet" />
</a>
{% endif %}3. Tanpa Layout
Karena halaman ini biasanya buat dicetak atau disimpan, seringnya gak butuh header dan footer. Pakai layout none atau layout kustom, sesuai 22 Layouts > Layout Alternatif:
{% layout none %}
<!-- template content -->Tapi kalau layout none, kamu harus nulis <html>, <head>, dan content_for_header sendiri di template. Kalau template gift card gak muncul di menu theme editor, kemungkinan content_for_header-nya lupa.
4. Preview
Di theme editor, buka dropdown di atas, Templates > Others > Gift card.
metaobject
Template buat halaman metaobject (artis, penulis, toko fisik). Syaratnya definisi metaobject-nya punya capability onlineStore (biar bisa dirender sebagai halaman) dan sebaiknya renderable (biar ada pengaturan SEO).
Lokasinya di sub-folder dengan nama tipenya:
└── theme
├── templates
| |── metaobject
| | |── {type}.jsonAturannya:
- Template pertama yang dibikin buat sebuah tipe jadi template default, otomatis dipakai semua entri aktif tipe itu. Setelah itu boleh bikin alternate.
- Template-nya kosong waktu dibikin di theme editor, merchant harus nambahin section sendiri.
- Semua section yang tersedia buat template atau section group manapun otomatis tersedia di template metaobject.
- Field-nya diakses lewat object
metaobjectdari 15 Objects Konten > 4. Di Template Metaobject.
Ringkasan
- Template JSON gak bisa berisi kode, semua HTML dan Liquid ada di section utamanya (
main-*.liquid). index.jsonbuat halaman depan,content_for_indexcuma buatindex.liquidlama.- Template produk wajib punya form produk dengan input
quantity, pemilih varian yang ngisi<input name="id">, danpayment_buttondi dalam form. Properti baris lewatproperties[nama]di dalam form. product.remote.seller.jsonbuat produk toko lain, isinya cuma info inti, tanpa metafield, dan awalanremote.dilindungi.- Template koleksi butuh paginasi (maksimal 50 per halaman), urutan lewat
?sort_by=, dan JavaScript pengurut harus mempertahankan parameter yang udah ada. list-collectionsurutannya abjad, buat urutan kustom pakai menu danlink.object. Sediain gambar cadangan buat koleksi tanpa gambar.- Form keranjang butuh
action="{{ routes.cart_url }}",method="post", dan submit bernamacheckout. Hapus item lewaturl_to_remove, update kuantitas lewatupdates[]. - Atribut
form="cart"bikin input catatan dan atribut keranjang bisa ditaruh di section lain tapi tetap ikut terkirim. - Properti baris yang berisi
/uploads/itu file, tampilin sebagai link. Item sama dengan properti beda jadi baris terpisah. - Form komentar butuh
comment[author],comment[email],comment[body], dan komentar di-paginate. - Form pencarian butuh
action="{{ routes.search_url }}"dan input bernamaqyangvalue-nyasearch.terms | escape. - 404 harus ngasih jalan keluar, password punya form
storefront_passworddan formcustomerdengan tagprospect, password page. gift_card.liquidwajib Liquid, di-host dicheckout.shopify.com, biasanyalayout nonetapi jangan lupacontent_for_header.- Template metaobject di
templates/metaobject/{type}.json, butuh capabilityonlineStore, dan template pertama jadi default.
Bab selanjutnya kita bahas empat template yang outputnya bukan halaman HTML tapi file teks buat mesin: robots.txt buat mesin pencari, dan agents.md, llms.txt, llms-full.txt buat AI agent.