Skip to main content

Headless CMS installation

Testimonial Widgets in a Headless CMS

A headless CMS hands your front end data, not finished pages, so pasting embed code into a rich text field is the wrong move. Model the widget as content instead: editors pick where it goes and type an id, and the code that turns that id into a widget stays in your repository.

  • A schema object editors can drop into a page
  • A renderer that turns the stored id into a div
  • One script tag in the shared layout

Code generator

Model, render, load: the three pieces

Enter your widget id to see it in the sample output. The schema is written for Sanity Studio; for another CMS, create the same two fields in its content model and keep the renderer and layout pieces as they are.

Shown in StoryProof when you copy a widget’s embed code.

Part 1 · Content model: a widget object editors can insert

schemaTypes/testimonialWidget.ts

// schemaTypes/testimonialWidget.ts (Sanity Studio)
import { defineField, defineType } from 'sanity';

export const testimonialWidget = defineType({
  name: 'testimonialWidget',
  title: 'Testimonial widget',
  type: 'object',
  fields: [
    defineField({
      name: 'widgetId',
      title: 'StoryProof widget id',
      type: 'string',
      description: 'Paste only the id, never the embed code.',
      validation: (Rule) => Rule.required(),
    }),
    defineField({ name: 'label', title: 'Note for editors', type: 'string' }),
  ],
});

// In a page or post body, allow it beside ordinary rich text:
// of: [{ type: 'block' }, { type: 'testimonialWidget' }]

Part 2 · Front end: render the stored id as a div

your rich text or block renderer

// The front end turns the stored id into markup.
// Framework neutral: adapt to your Portable Text or rich text renderer.
function renderTestimonialWidget(node) {
  const id = String(node.widgetId || '').trim();
  if (!id) return '';
  // Only the id comes from the CMS. The markup lives in code.
  const safe = id.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
  return '<div data-storyproof-widget="' + safe + '"></div>';
}

// Example output for one stored entry:
// <div data-storyproof-widget="YOUR_WIDGET_ID"></div>

Part 3 · Front end layout: load the script once for every page

shared layout, just before the closing body tag

<script src="https://app.storyproof.io/embed.js" async></script>

Insert a widget object into a draft page in the CMS, publish it, rebuild or revalidate the front end if your site is static, and load the page in a browser. One widget div should appear where the editor placed the object.

The widget id you type stays in your browser; nothing is sent.

The problem, solved

Why embed code and a headless CMS do not mix, and the content model fix

  • Problem 1

    Pasted embed code shows up as text

    Sanity, for one, stores rich text as an array of JSON objects, and your own renderer decides what markup comes out. A pasted script is just a string to it, so nothing runs.

  • Problem 2

    I do not want editors pasting scripts

    Letting a CMS field output raw HTML opens every editor account to script injection on the live site.

  • Problem 3

    Marketing wants different widgets per page

    A widget hard coded into a template means a developer ticket every time a landing page needs a different set of quotes.

  • Problem 4

    Who checks the quotes before they ship?

    If testimonials were typed into the CMS as text, anyone with publish rights could add an unchecked quote.

In StoryProof

Wire the CMS to StoryProof

Step 1 of 5

Add the widget type to the content model

Create an object or content type with a required text field for the widget id and an optional note for editors. In Sanity, add it to the of list of the body field beside block so it can sit between paragraphs.

Where should the widget be decided?

ApproachWho picks the widgetWatch out for
Widget object inside rich textEditors, per page, between paragraphsEvery renderer that reads the body needs the new case
Reference field on the page typeEditors, one slot per pageFixed position chosen by the template
Hard coded in a templateDevelopersA code change for every swap
Link to the hosted Wall of LoveNobody, it is one pageVisitors leave your site to read it

Before you start

What to know before you build it

  • No CMS app or plugin

    StoryProof has no Sanity, Contentful or other CMS integration. The CMS stores a plain id that your own code turns into the embed.

  • Client side navigation

    The script fills the widget divs present when it runs. If your front end changes pages without a full load and a widget mounted later stays empty, send visitors to that page with a full page link and tell StoryProof support.

  • Previews may not show it

    CMS preview panes often render in a sandbox or skip layout scripts. Judge the widget on the deployed page.

  • Badge and widget allowance

    Each widget counts toward your workspace allowance, and a "Powered by StoryProof" badge shows until Pro.

  • Docs as of 2026-11-13

    The schema follows the Sanity documentation as read on 2026-11-13. Nobody has followed these steps on a live project yet; other headless CMSs name their fields differently.

FAQ

Questions people ask

Short answers to what usually comes up next.

Can I store the whole embed snippet in a text field instead?

You can, but your front end would then have to output raw HTML from the CMS, which is the risk this setup avoids. Storing the id keeps the markup in reviewed code.

Does this work with Contentful, Strapi or Storyblok?

The pattern does: a short text field for the id, a renderer case, and one script in the layout. Field and renderer names differ, so follow your CMS documentation for those parts.

Will the testimonials be in my statically built HTML?

No. Your build outputs the empty div, and the script fills it in the visitor’s browser.

What if an editor types a wrong id?

The div renders but the widget will not appear. Add the id list to the field description, and check every new page on the live site after publishing.

Is there somewhere to send people without any code?

Yes. Every plan includes a hosted Wall of Love page with its own link, which an editor can add as an ordinary link.

Still have a question? Ask the StoryProof team or book a demo.

Give your CMS editors a widget id

Collect and approve testimonials in StoryProof, then let editors place widgets by id.

Sources

  1. String type, Sanity Docs. Checked 2026-11-13.
  2. Block type, Sanity Docs. Checked 2026-11-13.

Last updated 2026-11-13.