Customizing Add to Cart
precartupdatelistener is a hook you provide that runs immediately before sparklayer's own built in add to cart ui calls updatecart giving you a chance to modify the payload it's about to send window\ sparkoptions = { precartupdatelistener(input partial\<updatecart>) promise\<partial\<updatecart>>, }; input is the payload about to be sent to updatecart it's not the live cart (no items , totals , etc ), just whatever that widget is about to add return the (optionally modified) payload sparklayer sends whatever you return if you're calling updatecart yourself (e g a custom add to cart button) you don't need this hook at all just set customattributes directly on the products you send see modifying cart contents docid 1nctqxihryueptnowkol7 collecting customizations from the page the most common use case is collecting values from form fields on the product page and attaching them as customattributes to whatever's being added to the cart to achieve this, we recommend tagging every relevant input/select with data spark custom attributes for use in the precartupdatelistener shirt length shirt fabric cotton polyester window\ sparkoptions = { precartupdatelistener (input) => { if (!input products? length) { return input; } const nodes = document queryselectorall( 'input\[data spark custom attributes], select\[data spark custom attributes]', ); const handledradiogroups = new set(); const attrs = \[]; nodes foreach((el) => { const type = (el type || '') tolowercase(); if (type === 'radio') { if (handledradiogroups has(el name)) return; handledradiogroups add(el name); el = document queryselector( `input\[name="${css escape(el name)}"]\[data spark custom attributes]\ checked`, ); if (!el) return; // nothing selected in this radio group } else if (type === 'checkbox' && !el checked) { return; } const labelel = el id ? document queryselector(`label\[for="${css escape(el id)}"]`) null; const key = labelel? textcontent trim() replace(/ $/, '') || el name; const value = el tagname === 'select' ? el options\[el selectedindex]? textcontent trim() el value trim(); if (key && value) { attrs push({ key, value }); } }); if (!attrs length) { return input; } input products foreach((item) => { item customattributes = \[ (item customattributes ?? \[]), attrs]; }); return input; }, }; for just one or two fixed fields, it's often simpler to look them up directly rather than tagging + scraping window\ sparkoptions = { precartupdatelistener (input) => { if (!input products? length) { return input; } const shirtlength = document queryselector('#shirt length')? value; const shirtfabric = document queryselector('#shirt fabric')? value; if (!shirtlength && !shirtfabric) { return input; } const customshirt = input products find((p) => p sku === 'shirt custom'); if (!customshirt) { return input; } customshirt customattributes = customshirt customattributes ?? \[]; if (shirtlength) { customshirt customattributes push({ key 'shirt length', value shirtlength }); } if (shirtfabric) { customshirt customattributes push({ key 'shirt fabric', value shirtfabric }); } return input; }, }; attaching a file use the spark file upload field component to let customers upload a file (e g an image or design) on the product page, then attach it via the same hook property description allowed extensions a list of file extensions to allow, as a json array defaults to most common file extensions max file size maximum file size, in bytes max files maximum number of files required whether a file must be uploaded it dispatches spark file attachment changed fired whenever the file list changes, with event detail as an array of { filesize, filename, gid } (or null if all files were removed) spark file attachment failed fired when a file fails to upload you can add listeners for these events with the following javascript window\ addeventlistener('spark file attachment changed', (e) => { // e detail is an array of the file details in the following format // { // filesize number, // filename string, // gid string, // } // handle files }); window\ addeventlistener('spark file attachment failed', (e) => { // e detail format // { // filesize number, // filename string, // errorcode string, // } // // errorcode can be one of the following // "unsupported file type" // "file too large" // "default" // handle failed file }); precartupdatelistener and the upload field are usually defined in separate parts of your theme (e g sparkoptions in a layout file, the upload field in a product template) with no shared js scope between them a hidden input is a simple way to hand the uploaded file data from one to the other it doesn't have to be a hidden input specifically, precartupdatelistener just needs some way to read whatever you used to store it, whenever it runs store the event detail there, then pull just the gid s out of it for the custom attribute's value window\ sparkoptions = { precartupdatelistener (input) => { if (!input products? length) { return input; } const filesjson = document queryselector('#shirt design')? value; // < read back the hidden input from above if (!filesjson) { return input; } const gids = json parse(filesjson) map((file) => file gid); const customshirt = input products find((p) => p sku === 'shirt custom'); // < match your own custom line item sku if (!customshirt) { return input; } customshirt customattributes = \[ (customshirt customattributes ?? \[]), { key 'front design', value json stringify(gids) }, // < key is shown to the customer/staff as the attachment's label ]; return input; }, }; see modifying cart contents docid 1nctqxihryueptnowkol7 for the full shape of customattributes , including this same file value format when building updatecart calls manually