Hampir semua bab fitur sebelumnya nyebut “Section Rendering API” atau “Ajax API” sambil lalu: predictive search di 38 Search dan Predictive Search, pemilih varian di 39 Produk Variants dan High-variant Products, rekomendasi di 41 Produk Recommendations, Gift Card, dan Bundles, pickup di 49 Delivery Pickup Availability. Bab ini tempat dua API itu dibahas utuh.
Ajax API itu kumpulan endpoint yang bisa dipanggil theme lewat JavaScript buat ngelakuin aksi tanpa reload halaman: nambah ke cart, ngambil data produk, nampilin saran. Empat endpoint-nya:
- Cart: update line item, atribut, dan catatan cart.
- Product: ambil info produk manapun di katalog.
- Product Recommendations: rekomendasi produk, udah dibahas di 41 Produk Recommendations, Gift Card, dan Bundles > 7. Product Recommendations API.
- Predictive Search: saran produk/koleksi/halaman/artikel, udah dibahas di 38 Search dan Predictive Search > Predictive Search API.
Dan Section Rendering API: cara minta HTML section lewat request Ajax, buat ngupdate sebagian halaman tanpa reload. Aturan yang berlaku buat semua: pakai URL sadar locale (window.Shopify.routes.root) dari 47 Markets Multi Currency, Multi Bahasa, Localization > URL Sadar Locale.
Product API
Satu endpoint: GET /{locale}/products/{handle}.js, ngembaliin JSON produk berdasarkan handle-nya:
fetch(window.Shopify.routes.root + 'products/red-rain-coat.js')
.then(response => response.json())
.then(product => alert('The title of this product is ' + product.title));Responsnya object produk: id, title, handle, description, vendor, type, tags, price/price_min/price_max (subunit, 12900 = $129.00), available, price_varies, compare_at_price*, variants (dengan id, title, options, option1-3, price, available, sku, requires_shipping, taxable, barcode, dan kalau ada requires_selling_plan + selling_plan_allocations), images, featured_image, options, url, dan selling_plan_groups buat produk dengan selling plan.
Dua catatan:
- Semua nilai uang dalam presentment currency pembeli (mata uang sesi mereka), bukan mata uang toko. Buat tau mata uangnya, cek field
currencydi responscart.js. - Array
variantsmaksimal 250, batasan yang sama denganproduct.variantsdi Liquid. Buat produk high-variant, pakai?option_values=dan Section Rendering API dari bab 39.
Kapan pakai ini? Waktu kamu butuh data produk yang gak ada di halaman sekarang: quick view di koleksi, pemilih varian di kartu produk, atau bundle builder. Kalau datanya udah ada di halaman, product | json di Liquid (hati-hati, ini ngeload semua varian sampai 250) atau atribut data-* lebih murah.
Cart API
Ini API yang paling sering dipakai theme, buat cart drawer, tombol add to cart Ajax, dan update kuantitas tanpa reload. Contoh di sini disederhanain (gak selalu pakai callback).
1. Tambah: POST cart/add.js
Nambah satu atau banyak varian. id ID varian, quantity jumlahnya, banyak varian dengan nambah object di array items:
let formData = {
'items': [{
'id': 36110175633573,
'quantity': 2
}]
};
fetch(window.Shopify.routes.root + 'cart/add.js', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => {
return response.json();
})
.catch((error) => {
console.error('Error:', error);
});Karena body-nya JSON yang dirakit di JavaScript, Content-Type harus application/json. Alternatifnya, ambil form add to cart yang udah ada pakai FormData, yang otomatis bawa id, quantity, properties[...], dan selling_plan dari input form:
let addToCartForm = document.querySelector('form[action$="/cart/add"]');
let formData = new FormData(addToCartForm);
fetch(window.Shopify.routes.root + 'cart/add.js', {
method: 'POST',
body: formData
})
.then(response => {
return response.json();
})
.catch((error) => {
console.error('Error:', error);
});Ini cara yang lebih disarankan buat tombol add to cart, karena form-nya tetap jalan tanpa JavaScript (progressive enhancement), dan JavaScript cuma nyegat submit-nya. Perhatiin: dengan FormData, jangan set Content-Type manual, browser yang ngatur.
Respons: JSON object items berisi line item yang ditambahin (bukan seluruh cart), dengan id, key, title, price, line_price, quantity, variant_id, product_title, variant_title, options_with_values, featured_image, url, properties, selling_plan_allocation (kalau ada), dan field diskon original_price, final_price, line_level_discount_allocations.
Aturan penggabungan: kalau item udah ada di cart, quantity di respons itu jumlah baru totalnya. Tapi kalau item yang sama punya harga, properti, atau selling plan berbeda, mereka dipisah jadi line item sendiri. Perubahan harga biasanya dari diskon otomatis. Contoh tiga item dengan varian sama tapi beda (biasa, dengan selling_plan, dengan parent_id buat bundle) ngasilin tiga line item.
Line item properties lewat object properties:
items: [
{
quantity: 1,
id: 794864229,
properties: {
'First name': 'Caroline'
}
}
]Selling plan lewat selling_plan dengan ID plan-nya (dari 44 Subscriptions):
items: [
{
quantity: 1,
id: 794864229,
selling_plan: 183638
}
]Error (semua status 422, message: "Cart Error"):
- Jumlah yang diminta melebihi stok (minta 20, tersedia 10): cart nambahin maksimum yang tersedia, dan ngembaliin error
"You can't add more #{item.name} to the cart.". Jadi walaupun error, sebagian udah masuk. - Produk habis total:
"The product '#{item.name}' is already sold out.". - Produk belum habis tapi semua stoknya udah di cart:
"You can't add more #{item.name} to the cart.".
Tampilin description-nya ke pembeli, jangan cuma console.error.
2. Ambil: GET cart.js
Cart sebagai JSON. Nilai uang dalam presentment currency, cek field currency:
fetch(window.Shopify.routes.root + 'cart.js')
.then(response => response.json())
.then(cart => console.log(cart.item_count));Field level atas: token, note, attributes, original_total_price, total_price, total_discount, total_weight, item_count, items (array line item lengkap kaya di add.js), requires_shipping, currency, items_subtotal_price, cart_level_discount_applications. Ini padanan JSON dari object cart di 16 Objects Cart, Checkout, Order, dan Customer > cart. Cart kosong punya item_count: 0 dan items: []. Buat ngosongin cart yang udah ada, pakai cart/clear.js.
3. Update: POST cart/update.js
Ngupdate kuantitas line item, catatan, atau atribut, bisa sekaligus. Ini endpoint “borongan”.
Kuantitas lewat object updates, kuncinya variant_id atau key line item, nilainya kuantitas baru:
let updates = {
794864053: 2,
794864233: 3
};
fetch(window.Shopify.routes.root + 'cart/update.js', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ updates })
})
.then(response => {
return response.json();
})
.catch((error) => {
console.error('Error:', error);
});Atau pakai FormData dengan updates[794864053]:
var formData = new FormData();
formData.append("updates[794864053]", 2);
formData.append("updates[794864233]", 3);
fetch(window.Shopify.routes.root + 'cart/update.js', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => console.log(data));Kenapa key lebih aman dari variant_id: cart bisa punya banyak line item dengan variant_id sama (beda properti, atau diskon otomatis bikin harga beda). Kalau kamu pakai variant_id dan cocok ke banyak baris, cuma baris pertama yang diupdate. key (format variant_id:hash) unik per baris. Tapi key gak permanen: berubah waktu karakteristik baris berubah (properti, diskon). Jadi selalu ambil key dari respons terbaru. Kalau pakai variant_id, kunci boleh integer atau string; kalau key, harus string.
Perilaku lain: kalau variant_id gak ada di cart, update.js nambahinnya sebagai baris baru. Saran Shopify: pakai change.js buat ngubah item yang udah ada, add.js buat nambah. Dan hati-hati: update.js gak memvalidasi kuantitas buat varian yang udah di cart, jadi bisa nambah lebih dari stok.
Hapus dengan kuantitas 0:
let updates = {
794864053: 0,
794864233: 0
};Array posisi: kirim array angka yang ukurannya sama dengan jumlah line item, tiap angka jadi kuantitas baris sesuai urutan. Cart dengan 3 baris berkuantitas 1, 2, 3 diubah jadi 3, 2, 1:
body: JSON.stringify({ updates: [3, 2, 1] })Catatan cart:
{ note: 'This is a note about my order' }Atribut cart, object key-value:
{ attributes: { 'Gift wrap': 'Yes' } }Atau formData.append("attributes[Gift wrap]", "Yes").
Diskon: terapin kode diskon lewat discount, banyak kode dipisah koma, string kosong buat ngehapus semua:
{ discount: 'discount_code' }
{ discount: 'discount_code,discount_code2' }
{ discount: '' }Ini yang bikin kotak “Kode diskon” di cart page bisa jalan tanpa ke checkout.
Respons: JSON seluruh cart. Error 404 "Cannot find variant" kalau ID varian gak ada atau gak dipublikasi ke online store.
4. Ubah Satu Baris: POST cart/change.js
Ngubah quantity, properties, dan selling_plan satu line item yang udah ada di cart. Cuma satu baris per request.
Identifikasi baris lewat id atau line:
id:variant_id(integer atau string) ataukey(harus string). Rekomendasinyakeykarena alasan yang sama di atas.
{ 'id': 794864053, 'quantity': 3 }
{ 'id': '794864053:83503fd282b94a4737d2c95bd95db0b8', 'quantity': 3 }line: posisi baris di cart, dimulai dari 1. Ini cara paling aman buat cart yang punya varian duplikat.
{ 'line': 1, 'quantity': 3 }Kuantitas harus integer, 0 buat ngehapus:
{ 'line': 2, 'quantity': 0 }Properti: object key-value, dan menimpa seluruh object properties. Pasangan yang udah ada dan gak dikirim lagi hilang. Dan gak bisa ngeset properties jadi object kosong setelah pernah diisi, kalau mau ngehapus semua properti, hapus barisnya. Alternatifnya sembunyiin lewat properti privat (di bawah):
{ 'line': 2, 'properties': { 'gift_wrap': true } }Selling plan: ID plan buat ngeset, null atau string kosong buat ngehapus. Dua aturan: cuma line yang bisa dipakai buat identifikasi (bukan id), dan selalu kirim quantity karena kalau gak, dianggap 1:
{ 'line': 2, 'quantity': 2, 'selling_plan': 183638 }
{ 'line': 2, 'quantity': 2, 'selling_plan': null }Ini yang benerin contoh pemilih plan di cart dari 44 Subscriptions > 2. Pemilih di Cart: kirim line, quantity, dan selling_plan.
Respons: JSON seluruh cart. Kalau item gak ada di cart, change.js gak nambahin, tapi ngembaliin 400 "no valid id or line parameter".
❌ ngubah selling plan pakai id
{ 'id': 794864053, 'selling_plan': 183638 }✅ pakai line dan quantity
{ 'line': 2, 'quantity': 1, 'selling_plan': 183638 }5. Kosongin: POST cart/clear.js
Nge-set semua kuantitas jadi nol. Responsnya JSON cart kosong. Gak ngehapus atribut dan catatan cart.
6. Ongkos Kirim
Buat nampilin estimasi ongkir di cart (kalkulator “Estimate shipping”), dua endpoint yang dipakai berpasangan karena perhitungannya bisa lama:
POST /{locale}/cart/prepare_shipping_rates.jsonngemulai perhitungan buat tujuan tertentu. Responsnyanull.GET /{locale}/cart/async_shipping_rates.jsondengan parameter yang sama ngecek apakah udah selesai. Kalau belum,null. Kalau udah, arrayshipping_ratesdenganname,price,delivery_days, dan seterusnya.
/{locale}/cart/prepare_shipping_rates.json?shipping_address%5Bzip%5D=K1N+5T2&shipping_address%5Bcountry%5D=Canada&shipping_address%5Bprovince%5D=Ontario/{locale}/cart/async_shipping_rates.json?shipping_address%5Bzip%5D=K1N+5T2&shipping_address%5Bcountry%5D=Canada&shipping_address%5Bprovince%5D=OntarioParameternya shipping_address[zip], shipping_address[country], shipping_address[province] (di-URL-encode). Polanya: panggil prepare, terus polling async tiap beberapa ratus milidetik sampai bukan null.
Ada juga GET /{locale}/cart/shipping_rates.json yang langsung ngembaliin hasil, tapi gak disarankan karena bisa lama dan kena throttle.
7. Properti dan Atribut Privat
Buat nempelin info ke line item atau cart yang gak boleh kelihatan pengunjung (ID internal, data app), tapi tetap kelihatan merchant di Order details admin. Dua-duanya disembunyiin di checkout.
Line item property privat: awali key dengan _:
items: [
{
'quantity': 2,
'id': 794864229,
'properties': {
'_foo': 'bar'
}
}
]Bisa campur privat dan publik:
items: [
{
quantity: 2,
id: 794864229,
properties: {
'_foo': 'bar',
'gift_wrap': true
}
}
]Peringatan: properti privat tetap muncul di line_item.properties Liquid dan di respons Ajax API. Nyembunyiin di storefront itu tugas theme. Saring yang diawali _:
{% for property in line_item.properties %}
{% assign first_character_in_key = property.first | slice: 0 %}
{% unless first_character_in_key == '_' %}
{{ property.first }}: {{ property.last }}
{% endunless %}
{% endfor %}Ini alasan __shopify_send_gift_card_to_recipient dari 41 Produk Recommendations, Gift Card, dan Bundles gak muncul di cart, kalau theme-nya nyaring dengan bener.
Cart attribute privat: awali dengan __ (dua garis bawah). Bedanya: atribut privat gak muncul sama sekali di cart.attributes Liquid maupun Ajax API, jadi gak perlu modifikasi theme. Bonusnya, gak ngaruh ke render halaman, jadi caching halaman lebih efektif.
8. Bundled Section Rendering
Ini fitur yang bikin cart drawer modern jalan. Daripada dua request (update cart, terus fetch section buat ngerender ulang drawer), kamu bisa minta HTML sampai lima section di dalam request Cart API yang sama. Tersedia di cart/add, cart/change, cart/clear, dan cart/update.
Tambahin parameter sections (daftar dipisah koma atau array) ke data request:
{
items: [
{
id: 36110175633573,
quantity: 2
}
],
sections: "cart-items,cart-icon-bubble,cart-live-region-text,cart-footer"
}Bawaannya section dirender dalam konteks halaman sekarang (dari header Referer). Buat konteks lain, kasih sections_url yang harus diawali / dan boleh punya query parameter:
{
sections: "cart-items,cart-icon-bubble,cart-live-region-text,cart-footer",
sections_url: "/cart?some_param=foo"
}HTML-nya ada di kunci sections di JSON respons, dengan ID yang sama:
{
attributes: {},
cart_level_discount_applications: [],
currency: "CAD",
item_count: 1,
items: [{…}],
items_subtotal_price: 100100,
note: null,
original_total_price: 100100,
requires_shipping: true,
sections: {
cart-items: "\n\n\n\n\n",
cart-icon-bubble: "1 item\n ",
cart-live-region-text: "New subtotal: $1,001.00 CAD\n",
cart-footer: "\n \n \n\n\n\n\n\n\n\n"
},
token: "Z2NwLXVzLXdlc3QxOjAxSjBQTVk1Sjc5NVFKTjNOVlhLWENXQUI1?key=0d9909213054e22d092152de385763f0",
total_discount: 0,
total_price: 100100,
total_weight: 1000,
}Perhatiin empat section yang diminta Dawn: item cart, bubble jumlah di ikon cart header, teks live region buat screen reader (“New subtotal: …”), dan footer cart. Satu request, semua bagian halaman yang bergantung cart ke-update.
Error: section dirender setelah modifikasi data selesai, jadi error render gak ngaruh ke status respons. Section yang gagal jadi null, siapin penanganannya. Tapi nilai gak valid buat sections atau sections_url (misalnya sections_url tanpa / di awal) bikin seluruh request 400, walaupun sisanya sebenarnya berhasil.
Section Rendering API
Minta HTML markup section theme lewat request Ajax, buat ngupdate konten tanpa reload seluruh halaman. Contoh: paginasi hasil pencarian tanpa reload. Kalau tujuannya update berdasarkan perubahan cart, pakai bundled section rendering di atas.
1. Minta Banyak Section: sections
Parameter sections ngerender sampai lima section, dikenali dari ID-nya. Responsnya JSON dengan pasangan ID dan HTML-nya. Bisa daftar dipisah koma atau array:
?sections=main-password-header,sections--1234__header
?sections[]=main-password-header§ions[]=sections--1234__headerSection bisa dirender dalam konteks halaman manapun dengan nempelin parameter ke URL halaman itu: /?sections=... buat root, /collections/featured?sections=... buat halaman koleksi. Section mewarisi konteks Liquid halaman yang diminta: /products/{handle}?sections=X ngerender X dengan data produk itu, /collections/{handle}?sections=X dengan data koleksi itu. Ini yang dipakai pickup availability (/variants/{id}/) dan pemilih varian (/products/x?option_values=).
Setting section gak bisa dikirim lewat API. Kalau section-nya ada di template atau dirender statis, setting yang ada dipakai. Kalau gak, nilai default.
function handleResponse() {
JSON.parse(this.responseText);
}
const request = new XMLHttpRequest();
request.addEventListener('load', handleResponse);
request.open('GET', '/?sections=main-password-header,sections--1234__header', true);
request.send();Responsnya:
{
"header":"<div id=\"shopify-section-main-password-header\" class=\"shopify-section\">\n<!-- section content -->\n</div>",
"footer":"<div id=\"shopify-section-sections--1234__header\" class=\"shopify-section shopify-section-group-header-group\">\n<!-- section content -->\n</div>"
}Query parameter yang dihormati waktu render halaman penuh (q, page, sort_by, filter.*) juga dihormati waktu render section. Jadi /collections/all?sections=product-grid&page=2&filter.v.availability=1 ngerender grid halaman 2 yang udah difilter.
Error: section yang gagal dirender (termasuk yang gak ada di theme yang dipublikasi) dikembaliin sebagai null. Status HTTP bisa 200 tapi ada section null di dalamnya. Selalu cek.
2. Minta Satu Section: section_id
Alternatif buat satu section: parameter section_id, responsnya langsung HTML (bukan JSON):
?section_id=main-password-headerBisa dipakai di konteks halaman manapun juga. Kalau ID-nya gak ada, status 404. Ini yang dipakai contoh predictive search dan rekomendasi: fetch, response.text(), parse pakai DOMParser, ambil isinya.
3. Nemuin Section ID
Dua cara:
- Di Liquid lewat
section.iddari 17 Objects Lainnya > section, biasanya dimasukin ke atributdata-section-idbiar JavaScript bisa baca. - Ekstrak dari atribut
idpembungkus section, yang formatnya:
<div id="shopify-section-[section-id]" class="shopify-section">
<!-- section content -->
</div>Buat section yang dirender statis ({% section 'social' %}), ID-nya nama file: social. Buat section di template JSON atau section group, ID-nya dinamis biar dua section bertipe sama gak tabrakan: sections--1234__header (di section group) atau template--5678__image_banner (di template JSON). Jangan hardcode ID dinamis ini, ambil dari section.id.
4. URL Sadar Locale
Sama kaya Ajax API lain. Dua basis yang umum:
fetch(window.Shopify.routes.root + "?sections={section-id}")
.then(res => res.json())fetch(window.location.pathname + "?sections={section-id}")
.then(res => res.json())Yang pertama konteks root (homepage), yang kedua konteks halaman sekarang (window.location.pathname udah termasuk prefix locale karena itu URL yang lagi dibuka). Pilih sesuai konteks Liquid yang kamu butuhin: grid koleksi butuh konteks koleksi (pathname), cart drawer bisa dari mana aja.
❌ URL hardcode, salah konteks di /fr
fetch("/?sections=cart-drawer")✅ sadar locale
fetch(window.Shopify.routes.root + "?sections=cart-drawer")Pola Umum: Add to Cart Ajax dengan Drawer
Ngerangkai semuanya, pola yang dipakai Dawn:
document.querySelector('form[action$="/cart/add"]').addEventListener('submit', async (event) => {
event.preventDefault();
const form = event.currentTarget;
const formData = new FormData(form);
formData.append('sections', 'cart-drawer,cart-icon-bubble');
formData.append('sections_url', window.location.pathname);
const response = await fetch(window.Shopify.routes.root + 'cart/add.js', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.status) {
// error 422: tampilin data.description ke pembeli
return;
}
for (const [id, html] of Object.entries(data.sections)) {
if (html === null) continue;
const fresh = new DOMParser().parseFromString(html, 'text/html').querySelector(`#shopify-section-${id}`);
document.querySelector(`#shopify-section-${id}`).replaceWith(fresh);
}
// buka drawer
});Satu request: nambah item, dapat HTML drawer dan bubble yang udah baru, ganti di DOM, buka drawer. Tanpa JavaScript, form-nya tetap submit biasa ke /cart/add dan pembeli mendarat di halaman cart. Cara deklaratifnya di masa depan: {% partial %} dari 35 Developer Preview tag {% block %} dan {% partial %}.
Ringkasan
- Ajax API = Cart, Product, Product Recommendations, Predictive Search. Selalu
window.Shopify.routes.root. Nilai uang dalam presentment currency dan subunit. - Product API
products/{handle}.js: JSON produk lengkap, maksimal 250 varian, buat data produk yang gak ada di halaman. cart/add.js:itemsdenganid,quantity,properties,selling_plan,parent_id; atauFormDatadari form (progressive enhancement). Item sama digabung kecuali beda harga/properti/plan. Error 422 dengandescription(stok kurang tetap nambah sebagian).cart.jsGET seluruh cart dengancurrency,items,attributes,note, total-total.cart/update.js:updates(kuncivariant_idataukey, 0 buat hapus, atau array posisi),note,attributes,discount. Nambah kalau varian gak ada, gak validasi stok, cocok banyak baris → cuma yang pertama.cart/change.js: satu baris lewatid(variant_id/key) atauline(mulai 1), ubahquantity,properties(menimpa total, gak bisa dikosongin),selling_plan(cumaline+ wajibquantity). 400 kalau item gak ada.cart/clear.jsnolin kuantitas tapi ninggalin atribut dan catatan. Ongkir:prepare_shipping_rates.jsonlalu pollingasync_shipping_rates.json.- Privat: properti
_key(harus disaring di theme), atribut__key(otomatis gak muncul, ramah cache). - Bundled section rendering:
sections(≤5) dansections_url(awali/) diadd/change/clear/update, HTML didata.sections, section gagal =null, parameter gak valid = 400. - Section Rendering API:
?sections=a,b(JSON, ≤5,nullkalau gagal) atau?section_id=a(HTML, 404 kalau gak ada), di URL halaman manapun buat mewarisi konteksnya, setting gak bisa dikirim, query parameter halaman dihormati. - Section ID:
section.iddi Liquid, atau dariid="shopify-section-[id]"; statis = nama file, JSON template/section group = ID dinamis (template--123__x,sections--456__y).
Bab selanjutnya kita bahas Online Store 2.0: apa yang berubah dari theme lama (template JSON, section di semua halaman, app block), dan langkah-langkah migrasi theme vintage ke arsitektur baru.