Drop Google Reviews into any Svelte 5 or SvelteKit app. Ships three ready-made layouts (badge, carousel, custom slot), pulls data from either the free Featurable API or the Google Places API, and works out of the box with SSR.
📖 Live docs & interactive examples →
- Features
- Installation
- Quickstart
- SvelteKit + SSR
- Layouts
- Props
- CSS customization
GoogleReviewtype- Troubleshooting
- Differences from
react-google-reviews - License
- 🛠️ Customizable: Three layout options with extensive CSS class/style prop overrides
- 🔎 SEO-friendly: JSON-LD structured data for search engines
- 💻 Responsive: Works on all devices and screen sizes
- ⚡ Fast: Lightweight — no React, no Emotion, no jQuery
- ✨ Free: Use the Featurable API at no cost
- 🌱 Fresh: Auto-updates reviews every 48 hours via the Featurable API
- ♿️ Accessible: WAI-ARIA compliant carousel and interactive elements
- 🪶 Lightweight: Scoped CSS, minimal dependencies (embla-carousel only)
npm install svelte-google-reviewsyarn add svelte-google-reviewspnpm add svelte-google-reviewsPrerequisites:
- Create a free account at https://featurable.com
- Create a new Featurable widget
- Click Embed > API and copy the widget ID
<script>
import { SvelteGoogleReviews } from 'svelte-google-reviews';
// Create a free widget at https://featurable.com
const featurableWidgetId = '842ncdd8-0f40-438d-9c...'; // use "example" for testing
</script>
<SvelteGoogleReviews layout="carousel" featurableId={featurableWidgetId} />Note: The Featurable API is free and provides caching, automatic updates, and unlimited reviews.
The Google Places API is limited to the 5 most recent reviews. Fetch them server-side and pass them directly:
<script lang="ts">
import { SvelteGoogleReviews } from 'svelte-google-reviews';
import type { GoogleReview } from 'svelte-google-reviews';
// Fetched server-side via Google Places API
let { reviews }: { reviews: GoogleReview[] } = $props();
</script>
<SvelteGoogleReviews layout="badge" {reviews} isLoading={false} />Note: Never expose your Google API key client-side. Always fetch reviews server-side (e.g. in a SvelteKit
+page.server.tsload function).
The component is SSR-safe: it renders a placeholder on the server and hydrates on the client. The carousel (embla) only initializes in the browser, so there is no window is not defined risk.
Create a +page.server.ts next to the page that renders <SvelteGoogleReviews />:
// src/routes/+page.server.ts
import { GOOGLE_PLACES_API_KEY, GOOGLE_PLACE_ID } from '$env/static/private';
import type { PageServerLoad } from './$types';
import type { GoogleReview } from 'svelte-google-reviews';
export const load: PageServerLoad = async ({ fetch }) => {
const url = `https://places.googleapis.com/v1/places/${GOOGLE_PLACE_ID}?fields=reviews,rating,userRatingCount&key=${GOOGLE_PLACES_API_KEY}`;
const res = await fetch(url);
const data = await res.json();
const reviews: GoogleReview[] = (data.reviews ?? []).map((r: any) => ({
reviewId: r.name ?? null,
reviewer: {
displayName: r.authorAttribution?.displayName ?? 'Anonymous',
profilePhotoUrl: r.authorAttribution?.photoUri ?? '',
isAnonymous: false,
},
starRating: r.rating,
comment: r.originalText?.text ?? r.text?.text ?? '',
createTime: r.publishTime ?? null,
updateTime: r.publishTime ?? null,
}));
return { reviews, averageRating: data.rating, totalReviewCount: data.userRatingCount };
};Then consume it in the page:
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { SvelteGoogleReviews } from 'svelte-google-reviews';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
<SvelteGoogleReviews
layout="carousel"
reviews={data.reviews}
isLoading={false}
structuredData
averageRatingOverride={data.averageRating}
totalReviewCountOverride={data.totalReviewCount}
/>The
$env/static/privateimport ensures your API key never ships to the client. Cache the load withsetHeaders({ 'cache-control': 'public, max-age=3600' })to avoid hitting the Places quota on every request.
For Vite / Astro / plain Svelte apps, wrap the component in a client-only boundary if your SSR framework doesn't natively support hydration, and fetch reviews at build time or from your own backend.
Displays a compact badge with the average rating, star visualization, and a link to your Google profile.
<SvelteGoogleReviews layout="badge" featurableId={featurableWidgetId} />An interactive, autoplay carousel showing individual review cards.
<SvelteGoogleReviews layout="carousel" featurableId={featurableWidgetId} />Provides full control via a children snippet. The reviews array is passed to the snippet.
<SvelteGoogleReviews layout="custom" featurableId={featurableWidgetId}>
{#snippet children({ reviews })}
{#each reviews as review}
<div>
<strong>{review.reviewer.displayName}</strong>
<p>{review.comment}</p>
</div>
{/each}
{/snippet}
</SvelteGoogleReviews>Migration note from React: The React
rendererprop is replaced by Svelte 5'schildrensnippet pattern.
| Prop | Type | Default | Description |
|---|---|---|---|
featurableId |
string |
— | Featurable widget ID |
reviews |
GoogleReview[] |
— | Static reviews array (mutually exclusive with featurableId) |
isLoading |
boolean |
— | Controls loading state when passing reviews manually |
layout |
"carousel" | "badge" | "custom" |
"carousel" |
Layout variant |
widgetVersion |
"v1" | "v2" |
"v1" |
Featurable API version |
apiBaseUrl |
string |
"https://api.featurable.com" |
Custom API base URL |
nameDisplay |
"fullNames" | "firstAndLastInitials" | "firstNamesOnly" |
"firstAndLastInitials" |
How reviewer names are shown |
logoVariant |
"icon" | "full" | "none" |
"icon" |
Google logo style |
maxCharacters |
number |
200 |
Max characters before "Read more" truncation |
dateDisplay |
"relative" | "absolute" | "none" |
"relative" |
How review dates are shown |
reviewVariant |
"card" | "testimonial" |
"card" |
Card layout variant |
theme |
"light" | "dark" |
"light" |
Color scheme |
hideEmptyReviews |
boolean |
false |
Hide reviews without text |
disableTranslation |
boolean |
false |
Use original (untranslated) review text |
accessibility |
boolean |
true |
Enable ARIA roles and focus management |
structuredData |
boolean |
false |
Inject JSON-LD structured data for SEO |
brandName |
string |
page title | Brand name for structured data |
productName |
string |
page title | Product name for structured data |
productDescription |
string |
"" |
Product description for structured data |
totalReviewCountOverride |
number |
— | Required for structured data when passing reviews manually |
averageRatingOverride |
number |
— | Required for structured data when passing reviews manually |
errorMessage |
string |
"Failed to load Google reviews..." |
Custom error text |
loadingMessage |
string |
"Loading reviews..." |
Custom loading text |
| Prop | Type | Default | Description |
|---|---|---|---|
carouselSpeed |
number |
3000 |
Autoplay interval in ms |
carouselAutoplay |
boolean |
true |
Enable autoplay |
maxItems |
number |
3 |
Max slides visible at once (desktop) |
readMoreLabel |
string |
"Read more" |
Expand label for truncated reviews |
readLessLabel |
string |
"Read less" |
Collapse label |
showDots |
boolean |
true |
Show navigation dots |
getRelativeDate |
(date: Date) => string |
built-in | Custom relative date formatter |
getAbsoluteDate |
(date: Date) => string |
toLocaleDateString |
Custom absolute date formatter |
| Prop | Type | Default | Description |
|---|---|---|---|
profileUrl |
string |
auto-fetched | Google Business profile URL |
badgeLabel |
string |
"Google Rating" |
Badge heading |
badgeSubheadingFormatter |
(n: number) => string |
"Read our N reviews" |
Custom subheading |
Every element exposes a *ClassName and *Style prop for targeted overrides. Style props accept inline style strings (e.g. "color: red; font-size: 14px").
<SvelteGoogleReviews
layout="carousel"
featurableId="example"
reviewCardLightClassName="my-card"
reviewReadMoreLightStyle="color: blue;"
/>See the live Storybook docs for a full interactive reference of every prop.
type GoogleReview = {
reviewId: string | null;
reviewer: {
profilePhotoUrl: string;
displayName: string;
isAnonymous: boolean;
};
starRating: number; // 1–5
comment: string;
createTime: string | null;
updateTime: string | null;
};Interactive docs are hosted at ohmybugs.github.io/svelte-google-reviews — every prop, layout, and theme variant has a live example.
Run locally:
npm run storybookUnlike the React version, this package uses Svelte's scoped <style> blocks — no CSS import is required. If styles are missing, check that your bundler processes .svelte files from node_modules (SvelteKit and @sveltejs/vite-plugin-svelte do this by default; custom Vite configs may need include: ['svelte-google-reviews'] in the Svelte plugin options).
Shouldn't happen — the carousel initializes in onMount. If you see it, you're likely rendering the component before your framework's hydration boundary. In SvelteKit, ensure you're not calling it inside a +page.server.ts file.
That is a hard limit of the Places API. Use the Featurable API (free, includes caching) if you need all reviews.
- Featurable API: refreshes every 48 hours by default.
- Google Places API: cache your
+page.server.tsload withsetHeaders({ 'cache-control': 'public, max-age=3600' })to avoid rate limits — but don't cache longer than a few hours or new reviews won't appear.
Make sure the reviews prop is identical between server and client renders. If you compute anything time-based (e.g. relative dates) at the top level, pass a stable value or override getRelativeDate with a deterministic formatter.
Import types explicitly:
import type { GoogleReview } from 'svelte-google-reviews';If you see Cannot find module 'svelte-google-reviews' or its corresponding type declarations, ensure your tsconfig.json uses "moduleResolution": "bundler" or "NodeNext".
| Feature | React | Svelte |
|---|---|---|
| Custom layout | renderer prop (render function) |
children snippet |
| Style props | React.CSSProperties object |
inline style string |
| Error/loading content | React.ReactNode |
string |
| Carousel library | react-slick |
embla-carousel-svelte |
| CSS | Emotion CSS-in-JS | Scoped <style> blocks |
| CSS import required | Yes (dist/index.css) |
No |
MIT — see LICENSE. By using the Featurable API you agree to the Featurable Terms of Service.
- react-google-reviews by Featurable — the original library this is adapted from
- embla-carousel — carousel engine (MIT)