Skip to content
Hazelsong is still being built.Skip to content
Hazelsong

Publish

Twenty-three ways to put them on a page

Two lines: a script tag and a div. Choose the layout the page actually needs — a wall, a marquee, a rating badge, a single pull quote — and change it later by editing those same two lines, because the layout is named in them.

PlanEvery plan, review markup included. Loading the widget from your own domain: Starter and Pro. All twenty-three layouts. Counted events a month, shared by your projects: 100 on Free, 2,200 on Starter, 8,000 on Pro.

How it works

Widgets, step by step

  1. It says who chose them

    Under every layout, by default, one line in small type: "Testimonials selected and published by Acme: not necessarily every review it received, and not an independent review service." Your hosted walls print the same sentence. A count or an average in a layout says published, because it is over what you approved, not over everything you received.

  2. Two lines, anywhere

    A script tag and the div it draws into. The same pair works on WordPress, Shopify, Webflow, Squarespace, Framer, Wix, a Next.js app, or a hand-written HTML file.

  3. Just this one, or a few chosen by hand

    On a published testimonial's card, "Copy the code for just this one" gives the two lines that draw it alone, large, with its rating and photo — beside a price, on a product page. only= with up to ten of them, separated by commas, draws those in the order you wrote them. Only what the widget may already show is drawn: approved, with the writer's permission for your website.

  4. Only those that mention a word

    Add q=plumbing to the tag (or type it on Walls and widgets, or on a named wall) and only the testimonials whose published words mention it at the start of a word are drawn: "plumb" finds plumbing and plumber. It is your own word, matched as written, with no AI and no synonyms, and it works with every other filter and with the limit.

  5. A link for the next one to write

    Add write=1 to the tag (or check it on Walls and widgets) and the widget draws one "Write a testimonial" link under the testimonials, to your own collection page, opening in a new tab; write= with one of your named forms links to that form instead. It is the same page for every reader, with the same questions and permission, and nothing about the reader is put in the link or counted.

  6. Its own words in your readers' language

    Add lang=es to the tag (or choose it on Walls and widgets) and the widget writes its own words in Spanish: the buttons, the labels, the average, "Reply from", "Asked afterward", and the lines that say a testimonial was written for a reward, what you say customers can generally expect, and who chose them. Spanish, Portuguese, French, German and Italian are offered, and lang=auto follows the language your page declares, with English for any other. Without lang= nothing changes.

  7. With a video, a voice or a picture first

    Choose "With a video, a voice or a picture first" as a widget's or a named wall's order (order=media on the tag) and the cards that carry a recording or an attached picture come first, then the rest, each group newest first. Nothing is left out: a limit takes the first cards of that order. It is decided by what a card carries, never by its rating, its score or its words, and the writer's own photo does not count.

  8. It takes your page's own type

    The widget does not fight the site it is in. Your brand kit — color, corner radius, logo — is applied from your project rather than pasted into the page.

  9. Every layout, on every plan

    All twenty-three layouts on the free plan, on every page of your own site. The layout is not what is being sold.

  10. Or let your coding agent paste them

    Every guide for a builder that runs a script, and the Walls and widgets page of your dashboard, has a prompt to paste into Cursor, Claude Code, Lovable, Bolt or a similar agent. It names the two lines, the id the script draws into, your slug (already filled in on the dashboard; a placeholder on the public guides), and where the lines go on that builder, in the same steps the guide shows. Check one box and it also says where the lift test's purchase line goes. It tells the agent to change nothing else, add no other analytics, and show you the diff before anything is published.

  11. Only on your own sites, if you say so

    On Walls and widgets, list your own sites — acme.com, or *.acme.com for every name under it, up to 20. Leave the list empty and the widget draws anywhere, as before. With a list, a page that is not on it — a staging copy, a preview deployment, a page somebody copied the tag onto — counts no view, play or click, enters no lift test, is sent no picture and no video, and draws one line instead of your testimonials: "These testimonials are shown on acme.com," with a link to the first site you listed. Your customers allowed your website, not every page the tag can be pasted into. localhost and 127.0.0.1 always draw, so you can test, and are never counted while the list has a site on it. The widget sends its page's host, and the server reads the address the browser says it is on.

  12. It says why it drew nothing

    Add ?hazelsong=debug to the address of any page with the widget and the browser's console says why it drew nothing or counted nothing: no element to draw into, a layout that does not exist, nothing approved with permission for your website, a page not on your list of sites, an automated browser or your own marked one, this month's delivery spent, layout B with nothing to draw, or where a lift test placed that browser. It is said in that browser only; nothing is sent for it.

  13. And tells you where it could not

    When the widget cannot draw on one of your pages — no element to draw into, drawn at no size, a layout that does not exist, an error while drawing — it sends us that page's host, its path without the query and with anything that looks like an id or an email address cut out, and which of the four it was. Only from a browser whose visitor your site says agreed (the lift test's answer, or consent=not_required), once per page, kind and day in that browser, at most 50 a project a day, and kept 14 days after the last one. Your report's "Is anything broken?" lists each page with the fix, such as adding the element where the testimonials should go.

  14. Served from a copy, so a busy page costs nothing to show

    What a widget draws comes from a copy of your published testimonials that is built once and kept, not from our database on every view, and the host's network keeps each answer for 60 seconds. Approve, reject, delete or change a permission and the copy is thrown away at once: every reader sees the change within a minute. A widget and a wall draw from your 300 most recently published testimonials; the rating and the count are always over everything you have published.

  15. Counted, out of a month you can see

    Each view, video play and click a widget reports is one of the account's counted events for the month — 100 on Free, 2,200 on Starter, 8,000 on Pro, the lift test included — and one carrying a campaign tag counts as two. When the month is spent the widget stops reporting until the month turns and goes on drawing your testimonials everywhere.

  16. Sites with a Content-Security-Policy

    Every builder's guide lists the directives your header has to allow — script-src, connect-src, img-src, media-src, frame-src and style-src — with the exact addresses, read from the widget's own code, and the same list with your own domain in place of ours.

  17. Review markup, where Google accepts it

    Add markup=product, markup=course, markup=software or markup=event to the script tag, with name= for what the page is about (a choice on Walls and widgets writes it for you), and the page gets one block of review markup for that item: the rating of every testimonial the widget was given that was written on your own collection page, with a rating from 1 to 5 and no reward attached, and up to 10 of the reviews it draws, each named as its card names the writer. Imports, the API and feeds are left out, because Google asks that a page not gather ratings from other websites. A course also needs description=, an app price= (0 if it is free), an event start= and place=. The same figure is drawn in words under the wall — "Rated 4.8 out of 5 across 23 testimonials written directly to Acme and published by it" — because Google asks that a marked-up rating be visible on the page. It is worked out in the reader's browser from what the widget already received, so it costs nothing to serve.

  18. From your own domain

    On Starter and Pro, a project that answers at a domain of its own, such as testimonials.acme.com, can load the widget from it: the script, its testimonials, the pictures, the videos and the counts are all asked for at that address, and Walls and widgets writes the two lines that way when you choose it. That domain serves your project only; another project's widget asked for there is not found. A Content-Security-Policy then names your domain, and a blocker that judges a script by its address sees yours. Nothing else changes: the same copy, the same rules, the same counting.

  19. Its events, in your own analytics

    Add forward=1 to the script tag (a box on Walls and widgets does it for you) and each time a view, a video play or a click to where a testimonial was first written is counted, the widget also tells the analytics your page already loads: Google Analytics 4 or Tag Manager as testimonials_viewed, testimonial_video_played and testimonial_source_clicked, and Plausible, Fathom and Umami as "Testimonials viewed", "Testimonial video played" and "Testimonial source clicked". Two properties go with them, the layout and the testimonial's public id, except in Fathom, which takes the name alone. It also dispatches hazelsong:view, hazelsong:play and hazelsong:source_click on the wall's element for any other tool. The ask script takes forward=1 too, for "Testimonial form opened" and "Testimonial sent".

Your site's HTML
<script src="https://hazelsong.com/widget/YOUR-SLUG.js?template=wall" async></script>
<div id="hazelsong-wall"></div>
Drawn from the product's own markup and labels, with example words — not a real customer.

Safe by design

The rule that makes it safe to have

Clear boundaries

What it will not do

Every capability page ends with this list. It is the part that says what you are actually buying.

  • It never translates a testimonial with lang=: the writer's words, name, question and answer, and your own reply and note, are drawn exactly as written, in whatever language they were written in. Translating a testimonial is a feature of its own, on Pro, with the original kept.
  • It does not guess a reader's language from their browser: lang= is your choice, or your page's own declared language with lang=auto.
  • The translations of the reward line, the typical-results line and the line that says who chose the testimonials are ours and have not been reviewed by a lawyer in each country; the English stays the reference.
  • It will not use a card's rating, score or words to decide the order "with a video, a voice or a picture first": only what the card carries, and only when you choose that order.
  • It will not guess what a word means: q= matches the word as written, at the start of a word, and nothing else.
  • It will not draw a testimonial named in only= that it may not show: one not approved, not allowed on your website or withdrawn is never fetched, and nothing is drawn in its place.
  • It will not choose a rating for the person, or send some readers to one page and others elsewhere: the "Write a testimonial" link is the same page for every reader.
  • It does not track your visitors for us.
  • It does not slow your page down waiting for a font or a framework.
  • It will not show a testimonial you have not published.
  • It does not present your selection as every review you received, or as an independent review site: the line that says who chose them has no setting that removes it.
  • The agent prompt is not offered for Notion, Substack or Google Sites: they do not run a script, and their guides say what to use instead.
  • The agent prompt does not edit your site itself, and it cannot make an agent obey it. It is text you paste; read the diff your agent shows you before you accept it.
  • The list of sites is not a lock. The widget reads its page's own address and the server reads the address the browser says it is on; a program that leaves those out or writes something else in them is not stopped. It keeps the ordinary cases out of your figures and your customers' words off pages they did not agree to.
  • A report that the widget could not draw carries nothing about the reader: no address, no identifier, no referring site, nothing typed. It is the page and what went wrong, and nothing is kept in the reader's browser for it beyond a note of what that browser already reported today.
  • It will not report a fault from a visitor whose consent your site has not given: that note lives in their browser, so it waits for the same answer as the lift test (hazelsong.consent(true), consent=granted on the tag, Google Consent Mode, or consent=not_required where your visitors' law asks none), and without it nothing is kept or sent. ?hazelsong=debug still says what went wrong.
  • ?hazelsong=debug sends nothing anywhere. What it prints stays in the console of the browser that opened the page.
  • forward=1 never sends your analytics anything about the reader: no name, no word of a testimonial, no address, and not the lift test's group, which is a draw made for that reader's browser. It sends the layout and the testimonial's public id, and only when Hazelsong counts the same event, so never from an automated browser or your own marked one.
  • forward=1 never loads a script. It calls only what your page already loaded, and a page with none of those tools gets the hazelsong: events and nothing else.
  • It will not count without a figure. Past the month's counted events it reports nothing until the month turns (the 1st on Free, the billing day on Starter and Pro) — no view, play or click, and no lift test — and it never hides a testimonial because counting stopped.
  • It will not draw more than the newest few hundred of a very large collection. The rest stay published, in your export and on the API, and the rating on the wall is still over all of them.
  • It does not promise stars in search results. Google decides whether to show them, for which pages and when, and nothing on your page can make it.
  • It will not mark up your business itself. There is no Organization or LocalBusiness option, because Google does not show stars for reviews a business collected about itself; a tag that asks for one adds nothing and says why under ?hazelsong=debug.
  • It will not guess what a page is about, count a score out of 10 as a rating, or put a figure in the markup that is not drawn on the page. With nothing that qualifies, it adds no markup at all.
  • From your own domain it will not serve another project's widget, testimonials, pictures or counts.

Where it lives in the code: app/widget/[slug]/route.ts · lib/widget-markup.ts · scripts/widget-markup.mjs · lib/own-domain-rules.ts · scripts/own-domain-widget.mjs · lib/forward.ts · scripts/forward.mjs · lib/layouts.ts · lib/agent-prompt.ts · components/AgentPrompt.tsx · lib/embed-targets.ts · lib/allowed-sites.ts · lib/site-check.ts · components/AllowedSites.tsx · lib/widget-diagnosis.ts · lib/widget-faults.ts · scripts/allowed-sites.mjs · scripts/widget-diagnosis.mjs

Questions

Asked about this

From Help, where every answer is written from the product as it runs.

How much code is it?

Two lines: a script tag and a div. The layout is a query string on the script tag, or a data attribute on the div — both of which live in your page, so turning a wall into a marquee later means editing those two lines where you pasted them. There is nothing to rebuild at this end.

It shows nothing on my page.

Three usual causes, in order of likelihood: nothing is published yet (Pending is not published); the builder you are using strips script tags from block content, in which case the tag goes in the theme footer; or you are looking at the editor preview rather than the live page, which is normal on Webflow, Framer, Elementor and Squarespace.

Can I embed it in Notion or a newsletter?

No, and not because of us: neither runs a third-party script. Link the hosted wall in Notion, and use a testimonial image in the newsletter. Email has not run JavaScript for twenty years.

Does the widget slow my page down?

It is one small script with no framework behind it and no font to fetch. It draws after your page has drawn.

More on putting them on your site

This is running now

Open a project and use it. Nothing on this page is a roadmap.