Skip to content

JavaScript API

Markdown

The 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.

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.

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")
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
  • If the container is not ready (no data-review-ready attribute).
  • 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.

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.

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.

The script detects changes on the page by itself:

  • When an element carrying the data-review-mount attribute 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-order element 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.

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.

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>

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();
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 });
});
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.

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.