Other

Mocha for agencies and developers: embedding on client websites

This page is for agencies and developers who add Mocha to their clients' websites. Practices, law firms and consultancies can simply forward the link to their agency.

TL;DR in 5 steps

  1. Have the client create the Mocha account, with one space per client or location.
  2. In the space, copy the embed code under “Widget” with “Copy code”.
  3. Place the script tag exactly where the widget should appear in the content.
  4. In single-page apps, insert the script into a container with JavaScript after mount.
  5. Allow the Mocha domain in your CSP and exclude the script from JS optimisation.

Accounts and spaces

Mocha has no team or multi-user accounts. An account has one login (email and password, or Google), and every space belongs to exactly one account. There is currently no way to move a space to another account.

We therefore recommend that your client creates the account and subscribes to the plan themselves. The reviewers' consent, the consent log and the invoices then sit with the client and stay there if they change agency. You do not need a login to embed anything: the script tag is public, so your client can simply send you the copied code.

If you run your own account with one space per client instead, the reviews and consent records sit in your account. Check with your data protection adviser which agreements you need with your clients for that.

Create one space per client or location (Create a space). Each space has its own form, its own widgets and its own Google link on the thank-you page. The free plan includes one space and shows up to 50 approved reviews with a “Powered by Mocha” note. Pro costs €19 per month (14-day free trial) and adds unlimited spaces and reviews, your own logo and colours, and no Mocha note (Pricing).

The embed code

The dashboard generates the code: open the space, choose Widget under Embed in the space navigation, pick a widget under Choose a widget, adjust it under Appearance and click Copy code.

HTML
<script
  src="https://mocha.de/api/embed/your-space/widget.js"
  data-mode="wall"
  async></script>

How the script behaves:

  • Placement: the script finds its own tag via document.currentScript and inserts a <div data-mocha-widget="your-space"> directly after it. The widget appears exactly where the script tag sits.
  • Shadow DOM: the widget renders inside an open shadow root. Your page's CSS does not leak in and the widget's CSS does not leak out. Inherited properties such as the font family come from your page. Control width and outer spacing with the [data-mocha-widget] selector.
  • Asynchronous: with async, the script does not block page rendering. Until the data has loaded the widget has no height, so a minimum height on the surrounding container reduces layout shifts.
  • Live data: the widget fetches the approved reviews from Mocha on every page view. That response may be cached for 60 seconds and the script itself for an hour, so newly approved reviews appear without any change to the code.
  • Empty rather than an error: if the space is deactivated or has no approved review yet, the widget stays empty.
  • Several widgets: each script tag creates its own widget, including several on one page.

Data attributes

The dashboard only writes values into the code that differ from the default. If an attribute is missing, the default from the table applies.

AttributeValuesDefaultApplies to
data-modeslider, wall, single, badge and others from “Choose a widget”sliderall
data-themelight, darklightall
data-radiuscorner radius in pixels, 0, 12 or 24 in the dashboard12all with cards
data-shadownone, subtle, strongsubtleall with cards
data-font-sizesm, md, lgmdall
data-autoplaytrue, falseslider: on, portrait: offslider, portrait
data-speedmilliseconds, 3000 to 8000 in the dashboard4000 (portrait: 3000)slider, portrait
data-arrowsfalse hides themonslider, portrait
data-dotsfalse hides themonslider
data-columnsnumber of columns, 2 to 4 in the dashboard3wall, masonry
data-gapgap in pixels, 8, 16 or 24 in the dashboard16wall, masonry
data-alignleft, centerleftsingle
data-badge-stylepill, card, minimalpillbadge
data-starsfalse hides the staronbadge

The dashboard adds further options for individual widgets, such as scrolling speed or card width, to the code when needed. The accent colour for stars and highlights comes from the space, not from the code. The column count applies at every screen width, so the slider is a better fit for narrow areas.

Single-page apps: React, Next.js, Vue

A script tag in a template usually does not work as intended in a single-page app:

  • Browsers do not run scripts inserted via innerHTML or dangerouslySetInnerHTML.
  • React 19 moves <script async src="…"> from JSX into the <head> and removes duplicate scripts with the same URL (React documentation). The widget would then end up in the <head>.
  • next/script appends the script to the end of <body>, not where the component is.
  • Vue ignores <script> tags in client component templates.

Instead, insert the script element into a container with JavaScript after mount, and empty the container on unmount:

TSX
"use client";
import { useEffect, useRef } from "react";

export function MochaWidget({ slug, mode = "wall" }: { slug: string; mode?: string }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const container = ref.current;
    if (!container) return;
    const script = document.createElement("script");
    script.src = `https://mocha.de/api/embed/${slug}/widget.js`;
    script.async = true;
    script.dataset.mode = mode; // more, e.g. dataset.theme = "dark", dataset.fontSize = "lg"
    container.appendChild(script);
    return () => container.replaceChildren();
  }, [slug, mode]);

  return <div ref={ref} style={{ minHeight: 320 }} />;
}

The script still finds itself via document.currentScript and places the widget inside the container. In Vue the approach is the same: insert the element in onMounted and empty the container in onBeforeUnmount. Every insertion fetches the data again and counts as a view in the widget statistics. Alternatively, embed the review wall as an iframe (see below).

Tag managers

We do not recommend a tag manager for widgets in the page content. The widget appears wherever the script tag ends up in the DOM, and Google Tag Manager adds custom HTML tags at the end of <body>, which would put the widget below the footer.

If it has to be a tag manager, add a container with a fixed ID to the page template and have the tag insert the script there with JavaScript:

HTML
<!-- In the page template, where the widget should appear -->
<div id="mocha-reviews"></div>

<!-- Custom HTML tag in the tag manager -->
<script>
  (function () {
    var target = document.getElementById("mocha-reviews");
    if (!target) return;
    var s = document.createElement("script");
    s.src = "https://mocha.de/api/embed/your-space/widget.js";
    s.setAttribute("data-mode", "wall");
    s.async = true;
    target.appendChild(s);
  })();
</script>

Content Security Policy

If the website has a Content Security Policy, the widget needs these sources:

DirectiveSourceUsed for
script-srchttps://mocha.dewidget.js and the video player
connect-srchttps://mocha.deloading reviews, sending events for the widget statistics
img-srchosts of the review imagesphotos and video thumbnails; for imported Google reviews, profile photos from Google
style-src'unsafe-inline'the <style> element in the Shadow DOM and style attributes
media-srchost of the videosvideo player only
frame-srchttps://mocha.deiframe embeds only

A nonce does not help with style-src, because the widget creates its <style> element itself. Without this source the widget appears unstyled. You can see the exact image and video hosts in the Network tab of the developer tools. The widget does not work with require-trusted-types-for 'script', because it uses innerHTML.

Caching and optimisation plugins

Exclude widget.js from every feature that combines, moves or delays JavaScript until the first interaction. The script needs its own tag in place: it reads the space and options from it and uses it to position the widget. Copied into a bundle, it cannot find its tag and stays empty. Notes on common plugins are on the WordPress page.

Other ways to embed

Review wall as an iframe: below the embed code, iFrame embed opens an <iframe> code for https://mocha.de/wall/your-space. The page accepts the parameters theme (light, dark), columns (2, 3, 4) and limit (6, 12, 24, default 12). The iframe does not resize to its content, so set height to suit. This works for systems that do not allow scripts in the content.

A single video: for an approved video review, open Testimonials, open the review's details (View details) and find the code in the Embed this video section. The player also renders in a Shadow DOM directly after its script tag, so the same rules for SPAs apply.

HTML
<script
  src="https://mocha.de/api/embed/video/VIDEO-ID/player.js"
  data-id="VIDEO-ID"
  async></script>

Form and QR code: the form lives at https://mocha.de/s/your-space; copy the link with Copy link in the space navigation. Create the matching QR code under Embed in the QR code section.

Privacy on the client's website

These points describe what happens technically. They are not legal advice.

  • Visitors' browsers load the script and the review data from mocha.de, and photos and videos from the media servers Mocha uses for them. As with any request, these servers receive the IP address. Profile photos of imported Google reviews come from Google.
  • When the widget is shown it sends a “view” event to Mocha, and a “click” event when someone clicks a review card. Each event contains the space, the widget style, the review where relevant, and the full page address including URL parameters. Mocha also stores the browser's user agent, but not the IP address, and deletes individual events after 13 months. This is what the space's widget statistics are built from.
  • With data-track="false" on the script tag the widget sends no events, and the statistics stay empty for that embed.
  • The widget sets no cookies and does not use local storage in the browser.

Mocha's privacy policy (in German) describes how Mocha processes this data. Whether and how the client's own privacy policy should mention the widget under the GDPR is a question for the client and their data protection adviser.

Frequently asked questions

Are there team logins for agencies?

No. Mocha has no team or multi-user accounts. We recommend that your client creates the account. To embed the widget, all you need is the code they send you. Alternatively, you can run your own Pro account with one space per client.

Can I style the widget with my own CSS?

Only from the outside. The widget renders in a Shadow DOM, so your page's CSS does not reach inside. Style it with the data attributes and the space's accent colour, and control width and spacing through the element with the data-mocha-widget attribute. The widget takes its font from your page.

Why doesn't the widget appear?

Common causes: an optimisation plugin has combined or delayed the script, the Content Security Policy blocks mocha.de, the script was inserted via innerHTML or moved into the head by React, or the space is deactivated or has no approved review yet. CSP errors show up in the developer tools console.

Does the widget set cookies?

No, and it does not use local storage either. It does send an event with the page address to Mocha when it is shown and when a review card is clicked, which feeds the widget statistics. Adding data-track="false" to the script tag turns this off.

What if the CMS does not allow scripts in the content?

Embed the review wall as an iframe instead. You will find the code in the dashboard under Widget in the iFrame embed section. The iframe needs a fixed height because it does not resize to its content.

Ready for your first reviews?

Start for free, no credit card needed. Your form is ready in minutes.

Sign up for free