JavaScript API
MarkdownThe script's window.Sayproof interface, its options, events and usage examples.
When the script loads, it creates the window.Sayproof object, finds the containers on the page and starts watching the page for changes. On most sites this interface is not needed: adding a container, removing it or changing its attributes is sufficient. When you need to manage the component from code, use the methods below.
Waiting for the script to be ready
Section titled “Waiting for the script to be ready”The script is loaded as a module, and your code may run before it. If window.Sayproof is not yet defined, wait for the sayproof:ready event:
function whenSayproofReady(callback) { if (window.Sayproof) callback(window.Sayproof); else window.addEventListener("sayproof:ready", () => callback(window.Sayproof), { once: true });}The examples on this page use this helper function.
Methods
Section titled “Methods”| Method | Purpose |
|---|---|
mount(container, options?) |
Binds the component to the container. options values take precedence over the container’s attributes. If the container is already bound, the component is updated with the new options. |
update(container, options) |
Merges the new options with the previous ones and updates the component. Also used to pass the signed-in customer’s status. |
unmount(container) |
Removes the component from the container. |
scan() |
Scans the page again: binds new containers and data-review-order elements, and cleans up those removed from the page. |
write(container) |
Opens the review writing flow. Returns true if the flow was opened, false if not. |
mountOrder(element, answer?) |
Builds the reviews section of the order page in the element. answer is the answer your server received from the GET /v1/viewer/order call; if it is not given, the data-review-order-state tag inside the element is read. Calling it again on the same element rebuilds the section with the new answer. |
version |
The contract version ("3") |
Options
Section titled “Options”| Option | Corresponding attribute |
|---|---|
product |
data-review-product |
scope |
data-review-scope ("variant" or "group") |
productName |
data-review-product-name |
locale |
data-review-locale. If it is not given anywhere, <html lang> is used, and failing that the channel’s language. |
layout |
data-review-layout ("list" or "grid") |
style |
data-review-style ("cards" or "sidebar"). If it is not given anywhere, the layout chosen for the channel in the panel is used. |
theme |
data-review-theme ("warm", "trust", "editorial", "minimal", "soft", "bold", "natural" or "night"). If it is not given anywhere or is not recognised, the design chosen for the channel in the panel is used. |
mode |
data-review-mode ("default" or "headless") |
gate |
data-review-gate |
siteKey |
data-review-site-key |
base |
data-review-base |
nonce |
The nonce value to add to the style tags |
viewer |
The signed-in customer’s status: { state, token, products, reviews, limits, asker }, "pending" or null |
When write returns false
Section titled “When write returns false”- If the container is not ready (no
data-review-readyattribute). - If a dialog is open or opening on the page.
- If the signed-in customer has already written a review for this product (
written). - If the signed-in customer’s status is still awaited (
data-review-viewer="pending").
In all other cases, the review form opens if the customer can write a review; otherwise the order verification opens first and, if verification succeeds, the flow moves to the form in the same dialog. The form never opens before the order has been verified on the server.
Events
Section titled “Events”| Event | Dispatched on | When |
|---|---|---|
sayproof:ready |
window |
The script has loaded; window.Sayproof can be used |
sayproof:submitted |
Container; on the order page the data-review-order element (bubbling) |
A review was submitted successfully. On a product page event.detail.product contains the product code; on the order page event.detail.order contains the order number and event.detail.line the ID of the order line. Because each item is sent separately on the order page, the event is dispatched for each item. |
A submitted review is published after approval; you therefore do not need to refresh the list immediately on the sayproof:submitted event.
Invitation links
Section titled “Invitation links”The invitation link carries the token in the part of the address after the # sign (#review_token=…); the script also reads the ?review_token=… parameter of older invitations. The script reads the value as soon as it loads and removes it from the address bar. The part after # is not sent to servers and does not reach the page address recorded by analytics tools (such as page_location). The form opens in the container that displays the invited product or, if there is no such container, in the first container on the page. If the link is invalid or has expired, an explanatory message is shown in the review writing area.
Watching for page changes
Section titled “Watching for page changes”The script detects changes on the page by itself:
- When an element carrying the
data-review-mountattribute is added to the page, the component is bound; when it is removed, the component is cleaned up. - When one of the container’s configuration attributes changes (for example a variant selection), the component is updated.
- When new server HTML arrives inside the container (for example when React re-creates the container), the script takes over this content.
- When a
data-review-orderelement is added to the page, the reviews section of the order page is built.
While the product is changing, the old content is made non-interactive (inert) until the new content has loaded. If the new load fails, the data-review-failed attribute is added to the container.
Examples
Section titled “Examples”Binding the component from code
Section titled “Binding the component from code”To pass the options from code instead of attributes:
<div id="reviews" data-review-mount></div>
<script type="module"> whenSayproofReady((sayproof) => { sayproof.mount(document.querySelector("#reviews"), { product: "PRODUCT-38", scope: "variant", locale: "en", layout: "list", style: "sidebar", theme: "trust", }); });</script>The site key and the API address are taken from the data-review-site-key and data-review-base values on the script tag. The data-review-mount attribute on the container is required so that the script can watch the container and invitation links can open in it. The examples assume that the whenSayproofReady function is defined on the page.
Changing the product from code
Section titled “Changing the product from code”const container = document.querySelector("#reviews");
document.querySelector("#size").addEventListener("change", (event) => { whenSayproofReady((sayproof) => sayproof.update(container, { product: event.target.value }));});Binding and removing in single-page applications (SPA)
Section titled “Binding and removing in single-page applications (SPA)”The script detects by itself when a container is added to or removed from the page. If you want to manage the component’s lifecycle from code, in React:
import { useEffect, useRef } from "react";
export function Reviews({ product, locale }: { product: string; locale: string }) { const container = useRef<HTMLDivElement>(null);
useEffect(() => { const element = container.current; if (!element) return; whenSayproofReady((sayproof) => sayproof.mount(element, { product, locale })); return () => window.Sayproof?.unmount(element); }, [product, locale]);
return <div ref={container} data-review-mount />;}In Vue:
<script setup>import { onBeforeUnmount, onMounted, ref, watch } from "vue";
const props = defineProps({ product: String, locale: String });const container = ref(null);
onMounted(() => { whenSayproofReady((sayproof) => sayproof.mount(container.value, { product: props.product, locale: props.locale }));});
watch( () => [props.product, props.locale], ([product, locale]) => window.Sayproof?.update(container.value, { product, locale }));
onBeforeUnmount(() => window.Sayproof?.unmount(container.value));</script>
<template> <div ref="container" data-review-mount /></template>Scanning the page immediately
Section titled “Scanning the page immediately”Because the script detects changes by itself, scan() is usually not needed. You can use it if you want binding to be completed immediately after replacing a large part of the page:
document.querySelector("#product-detail").innerHTML = html;window.Sayproof?.scan();Your own “Write a review” button
Section titled “Your own “Write a review” button”const container = document.querySelector("#reviews");
document.querySelector("#write-review").addEventListener("click", () => { const opened = window.Sayproof?.write(container) ?? false; if (!opened) container.scrollIntoView({ behavior: "smooth", block: "start" });});Forwarding submissions to your analytics tool
Section titled “Forwarding submissions to your analytics tool”document.addEventListener("sayproof:submitted", (event) => { window.dataLayer?.push({ event: "review_submitted", product: event.detail.product });});Passing the signed-in customer’s status
Section titled “Passing the signed-in customer’s status”const container = document.querySelector("#reviews");
fetch(`/api/sayproof/viewer?product=${encodeURIComponent(container.dataset.reviewProduct)}`, { credentials: "same-origin" }) .then((answer) => (answer.ok ? answer.json() : { state: "anonymous" })) .catch(() => ({ state: "anonymous" })) .then((viewer) => whenSayproofReady((sayproof) => sayproof.update(container, { viewer })));For the server side and the response values, see Signed-in customers.
Passing the order answer from code
Section titled “Passing the order answer from code”If the order detail page is built in the browser, fetch the answer from your own endpoint and pass it with mountOrder. Do not build the section when the answer has no items:
const element = document.querySelector("[data-review-order]");
fetch(`/api/sayproof/order?order=${encodeURIComponent(element.dataset.reviewOrder)}`, { credentials: "same-origin" }) .then((answer) => (answer.ok ? answer.json() : null)) .catch(() => null) .then((order) => { if (order?.lines?.length) whenSayproofReady((sayproof) => sayproof.mountOrder(element, order)); });/api/sayproof/order is your own server endpoint that asks Sayproof with the signed-in email address: Signed-in customers › Order detail page.