Display reviews

How to Add Google Reviews to a PHP or Static HTML Website

Add Google reviews to a PHP site with a cached server include, or to a static HTML site: the options AI crawlers can read, with copy-paste code and limits.

Updated

Most “Google reviews HTML code” you'll find online is a <script> tag. It drops a widget into the page for visitors, but the reviews never become part of your HTML. If you want Google and AI search engines to read them, the question is simple: does your site run server-side code?

Which method fits your site?

If your server runs PHP (or any other language), use the server include. If it doesn't, the AI-readable options need either a manual update or a build step.

Your siteMethodReviews in HTMLReadable by AIUpdates
PHP, PHP-based CMS (other than WordPress), Laravel, SymfonyServer include /api/embed/ID/htmlYesYesAutomatic (1-hour cache)
Any site behind Cloudflare, whatever the CMSCloudflare WorkerYesYesAutomatic (1-hour cache)
Static HTML with a build step (static site generator, CI)Fragment fetched at build timeYesYesEvery build
Static HTML without a build, Squarespace, Webflow, ShopifyPasted HTMLYesYesManual
Wix, or anything elseJavaScript snippet (iframe)NoNoLive
WordPressWordPress pluginYesYesNightly

The server include doesn't need an API key: the fragment URL is public and works on the Free plan.

How do you add Google reviews with PHP?

The server include returns the widget already rendered by Revys: review markup, CSS scoped to the widget, and JSON-LD, with no JavaScript. Your server fetches it and inserts it into the page before sending it to the visitor.

  1. Copy the code from Revys

    In your Revys dashboard, open My businesses › your business › On my website, click Put on my website and enter your site address. Revys recognizes a custom-built site (if it doesn't, pick Other or custom under “Not right?”); in 1. Server code, if you have a developer, pick PHP under “Your site is built with…”. The code already contains your widget's URL.

  2. Paste it into your .php file

    Paste it exactly where the reviews should appear (a .php file, not .html):

    <?php
    // Revys: reviews readable by Google and AI (1-hour cache, never blocking)
    $revysUrl = 'https://revys.io/api/embed/YOUR_WIDGET_ID/html';
    $revysCache = sys_get_temp_dir() . '/revys-' . md5($revysUrl) . '.html';
    if (!is_file($revysCache) || filemtime($revysCache) < time() - 3600) {
      $revysHtml = @file_get_contents($revysUrl, false, stream_context_create(['http' => ['timeout' => 2]]));
      if ($revysHtml !== false && $revysHtml !== '') { @file_put_contents($revysCache, $revysHtml); } else { @touch($revysCache); }
    }
    echo is_file($revysCache) ? file_get_contents($revysCache) : '';
    ?>
  3. Check the page source

    Open the page and view its source. The text of your reviews should be there. You can also run the URL through our AI review visibility checker.

That block follows three rules every include should:

  • A 2-second timeout (the HTTP context timeout), so your page never waits on Revys for longer.
  • A one-hour cache in the server's temp directory. Revys refreshes your Google reviews about every hour, so polling more often gains nothing.
  • Fallback to the last good copy: on failure, touch pushes the next attempt back an hour and the old file keeps being served.

Calling file_get_contents on a URL requires PHP's allow_url_fopen setting. It's on by default, but some hosts turn it off. If yours does, use the cURL version below.

cURL version with ETag (conditional requests)

The endpoint sends an ETag header and answers 304 Not Modified when the widget hasn't changed. This version takes advantage of that and works even with allow_url_fopen disabled:

<?php
// Revys: server include with cURL, 1-hour file cache and ETag
function revys_reviews(string $url, int $ttl = 3600): string
{
    $cache = sys_get_temp_dir() . '/revys-' . md5($url);
    $html  = is_file("$cache.html") ? (string) file_get_contents("$cache.html") : '';
    $etag  = is_file("$cache.etag") ? trim((string) file_get_contents("$cache.etag")) : '';

    if ($html !== '' && filemtime("$cache.html") > time() - $ttl) {
        return $html; // cache still fresh
    }

    $newEtag = '';
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 2,
        CURLOPT_TIMEOUT        => 2,
        CURLOPT_HTTPHEADER     => $etag !== '' ? ["If-None-Match: $etag"] : [],
        CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$newEtag) {
            if (stripos($line, 'ETag:') === 0) {
                $newEtag = trim(substr($line, 5));
            }
            return strlen($line);
        },
    ]);
    $body   = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

    if ($status === 200 && is_string($body) && $body !== '') {
        @file_put_contents("$cache.html", $body);
        if ($newEtag !== '') {
            @file_put_contents("$cache.etag", $newEtag);
        }
        return $body;
    }

    // 304 (unchanged) or error: keep the last copy, retry in an hour
    if (is_file("$cache.html")) {
        @touch("$cache.html");
    }
    return $html;
}

echo revys_reviews('https://revys.io/api/embed/YOUR_WIDGET_ID/html');

A 304 response isn't metered by Revys and doesn't transfer the fragment again.

Laravel and other PHP frameworks

Same idea, with your framework's tools. In Laravel, inside a controller or view component:

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

$revys = Cache::remember('revys-reviews', 3600, function () {
    try {
        $res = Http::timeout(2)->get('https://revys.io/api/embed/YOUR_WIDGET_ID/html');
        return $res->successful() ? $res->body() : '';
    } catch (\Throwable $e) {
        return '';
    }
});

Print it unescaped in Blade with {!! $revys !!}, or with Twig's raw filter. The fragment contains no executable script, and Revys escapes the review text.

What about a static HTML site?

Without server code, there's no method that is both automatic and readable by AI, unless you add a build step or Cloudflare. Here are the options, from simplest to most robust.

Option 1: paste the HTML Revys generates (manual updates)

In Revys, on your business's On my website page, click Put on my website (pick Other or custom if needed): in “Choose how to add your reviews”, method 2. HTML to paste gives you this HTML. Click Generate the HTML. Paste the result into your .html file, a Code block (Squarespace), an Embed element (Webflow) or a Custom Liquid section (Shopify). The reviews are now in your page's HTML, but they won't update on their own: re-copy the HTML when new reviews come in.

Option 2: fetch the fragment at build time

If your site has a build step (static site generator, CI pipeline), a script can inject the fragment on every build. Put two markers in your page:

<!-- revys:start -->
<!-- revys:end -->

Then run this script before deploying (Node.js 18 or later):

// scripts/revys-inject.mjs: node scripts/revys-inject.mjs index.html
import { readFile, writeFile } from "node:fs/promises";

const REVYS_URL = "https://revys.io/api/embed/YOUR_WIDGET_ID/html";
const file = process.argv[2] ?? "index.html";
const START = "<!-- revys:start -->";
const END = "<!-- revys:end -->";

const res = await fetch(REVYS_URL, { signal: AbortSignal.timeout(5000) });
if (!res.ok) {
  console.error(`Revys returned ${res.status}: ${file} left unchanged.`);
  process.exit(0);
}
const fragment = await res.text();
const page = await readFile(file, "utf8");
const i = page.indexOf(START);
const j = page.indexOf(END);
if (i === -1 || j < i) throw new Error(`Markers not found in ${file}`);
await writeFile(file, page.slice(0, i + START.length) + "\n" + fragment + "\n" + page.slice(j));

Schedule one build a day (a cron job in your CI or a scheduled deploy on your host) and your reviews stay fresh. If Revys doesn't answer, the page keeps the previous build's version.

Option 3: a Cloudflare Worker, if your site runs through Cloudflare

A Worker can insert reviews on the fly into any page Cloudflare serves, regardless of the host or CMS behind it. Add <div data-revys-include></div> to the page, then deploy this Worker (also in the dashboard, “Cloudflare Worker” option) on your site's route:

// Worker: replaces <div data-revys-include></div> with the reviews
const REVYS_URL = "https://revys.io/api/embed/YOUR_WIDGET_ID/html";

export default {
  async fetch(request) {
    const page = await fetch(request);
    if (!(page.headers.get("content-type") || "").includes("text/html")) return page;
    let html = "";
    try {
      const res = await fetch(REVYS_URL, {
        cf: { cacheTtl: 3600, cacheEverything: true },
        signal: AbortSignal.timeout(2000),
      });
      if (res.ok) html = await res.text();
    } catch {}
    if (!html) return page;
    return new HTMLRewriter()
      .on("[data-revys-include]", {
        element(el) {
          el.setInnerContent(html, { html: true });
        },
      })
      .transform(page);
  },
};

Option 4: the JavaScript snippet (no AI visibility)

The line <script src="https://revys.io/api/embed/YOUR_WIDGET_ID" async></script> works anywhere, Wix included. But it inserts an iframe pointing to revys.io: visitors see your reviews, AI crawlers don't, and Google attributes that content to revys.io rather than to your site.

Why not just use the JavaScript snippet?

Because AI search engines read your raw HTML. Vercel's December 2024 analysis found no major AI crawler (GPTBot, ClaudeBot and others) that executes JavaScript. Google does, but in a second rendering step described in its JavaScript SEO documentation. With a server include, the issue disappears: the reviews are part of the page.

Going further

How to embed Google reviews on your website compares every method. On Next.js, follow the Next.js integration. For reviews in your own markup (and English labels), the JSON API comes with the Pro plan, from $7.90/month for your first listing (pricing).

FAQ

What HTML code do I paste to show Google reviews on my website?

On a PHP site, paste the PHP block Revys gives you where the reviews should appear. On a site with no server code, Revys can generate the widget's full HTML for you to paste into the page, which you re-copy when new reviews come in.

Does the server include cost anything?

No. The /api/embed/ID/html URL is public, needs no API key, and works on the Free plan (one listing, with a small “Free widget by Revys” link). Only the JSON API requires the Pro plan.

What happens if Revys is down?

Nothing your visitors would notice: the request gives up after 2 seconds and the last cached version is shown. With no cache yet, the reviews area is simply empty.

Can ChatGPT read reviews added with the JavaScript snippet?

No. The snippet shows reviews in an iframe hosted on revys.io, so they aren't in your page's HTML, and major AI crawlers don't execute JavaScript.

My site is on Wix. Which method should I use?

Wix runs no server code and sandboxes pasted HTML in an iframe, so only the JavaScript snippet works there, without AI visibility.

Will the server include give me star ratings in Google?

No. Google doesn't show stars for reviews a business displays about itself on its own site. The fragment carries compliant Organization or LocalBusiness markup, never AggregateRating.

Sources

  1. file_get_contents (PHP manual)
  2. HTTP context options (PHP manual)
  3. cURL (PHP manual)
  4. Understand JavaScript SEO basics (Google Search Central)
  5. The rise of the AI crawler (Vercel, December 2024)

Related