Shopify product registration
Append validated product registrations to a customer-owned Shopify JSON metafield.
Complete direct recipe
{% if customer %}
{% assign saved_product_registrations = customer.metafields.custom.product_registrations.value %}
<script type="application/json" id="fr-registrations-initial">
{% if saved_product_registrations != blank %}{{ saved_product_registrations | json }}{% else %}[]{% endif %}
</script>
<form id="fr-registration-form">
<fieldset>
<legend>Register a product</legend>
<label for="fr-registration-product">Product handle</label>
<input id="fr-registration-product" name="product_handle" type="text" autocomplete="off" required>
<label for="fr-registration-serial">Serial number</label>
<input id="fr-registration-serial" name="serial" type="text" autocomplete="off" required>
<label for="fr-registration-date">Purchase date</label>
<input id="fr-registration-date" name="purchase_date" type="date" required>
</fieldset>
<button id="fr-registration-submit" type="submit">Register product</button>
<p id="fr-registration-status" aria-live="polite"></p>
</form>
<section aria-labelledby="fr-registration-list-heading">
<h2 id="fr-registration-list-heading">Registered products</h2>
<p id="fr-registration-empty">No products are registered yet.</p>
<ul id="fr-registration-list"></ul>
</section>
<script>
const registrationForm = document.getElementById("fr-registration-form")
const registrationButton = document.getElementById("fr-registration-submit")
const registrationStatus = document.getElementById("fr-registration-status")
const registrationList = document.getElementById("fr-registration-list")
const registrationEmpty = document.getElementById("fr-registration-empty")
let registrations = JSON.parse(document.getElementById("fr-registrations-initial").textContent)
function renderRegistrations() {
registrationList.replaceChildren()
registrationEmpty.hidden = registrations.length > 0
for (const registration of registrations) {
const item = document.createElement("li")
item.textContent = `${registration.product_handle} — ${registration.serial} — purchased ${registration.purchase_date}`
registrationList.append(item)
}
}
async function readRegistrationResponse(response) {
const contentType = response.headers.get("content-type") || ""
if (!contentType.includes("application/json")) {
throw new Error("FieldsRaven returned a non-JSON response. Please try again.")
}
try {
return await response.json()
} catch (error) {
throw new Error("FieldsRaven returned invalid JSON. Please try again.")
}
}
async function saveRegistrations(nextRegistrations) {
const config = window.FR_CUSTOM__CUSTOMER_PRODUCT_REGISTRATIONS
try {
const response = await fetch("/apps/raven/create_metafield", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ raven: {
raven_id: config.ravenId,
resource_id: config.resourceId,
raven_mac: config.ravenMac,
value: JSON.stringify(nextRegistrations)
} })
})
if (response.status === 429) throw new Error("Too many requests. Wait a moment and try again.")
const result = await readRegistrationResponse(response)
if (!response.ok) {
const message = typeof result?.message === "string" ? result.message : "FieldsRaven rejected the registration."
throw new Error(message)
}
return result
} catch (error) {
if (error instanceof TypeError) throw new Error("Network error. Check your connection and try again.")
throw error
}
}
registrationForm.addEventListener("submit", async (event) => {
event.preventDefault()
const formData = new FormData(registrationForm)
const serial = formData.get("serial").trim()
const productHandle = formData.get("product_handle").trim()
const purchaseDate = formData.get("purchase_date")
if (!productHandle || !serial || !purchaseDate) {
registrationStatus.textContent = "Complete every field before submitting."
return
}
if (registrations.some((registration) => registration.serial.toLowerCase() === serial.toLowerCase())) {
registrationStatus.textContent = "That serial number is already registered."
return
}
const nextRegistrations = [...registrations, {
product_handle: productHandle,
serial,
purchase_date: purchaseDate,
registered_at: new Date().toISOString()
}]
registrationButton.disabled = true
registrationButton.setAttribute("aria-busy", "true")
registrationStatus.textContent = "Saving…"
try {
await saveRegistrations(nextRegistrations)
registrations = nextRegistrations
renderRegistrations()
registrationForm.reset()
registrationStatus.textContent = "Saved. FieldsRaven accepted and queued the registration update."
} catch (error) {
registrationStatus.textContent = error.message
} finally {
registrationButton.disabled = false
registrationButton.setAttribute("aria-busy", "false")
}
})
renderRegistrations()
</script>
{% else %}
<p><a href="/account/login">Log in</a> to register a product.</p>
{% endif %}Data shape and metaobjects
Last updated