This guide covers how to add Buying Buddy widgets to React-based applications and React-powered website builders. It is intended for developers comfortable with React, JSX, and modern JavaScript tooling.
Applies to: Next.js, Gatsby, Remix, Vite + React, Create React App, and React-based site builders. For Framer, follow the dedicated Install Buying Buddy on Framer guide, which uses a ready-made single-file component.
How to Install
The recommended way to add Buying Buddy to a React project is the official package, @buyingbuddy/react. It is built specifically for React and handles the widget lifecycle correctly, including in single-page apps and AI website builders. A manual <script>-based method is also available as a fallback for the rare project that cannot add an npm dependency.
Method 1 — The @buyingbuddy/react package (Recommended)
Idiomatic React components — no script tags to manage
An npm package that wraps the Buying Buddy widget in two React components — <BuyingBuddyProvider> and <BuyingBuddyWidget>. The provider loads the Buying Buddy runtime for you, so there is no script tag to add by hand. This is the right choice for virtually every React project — Vite, Create React App, Next.js, Gatsby, Remix, and React-based AI site builders alike.
Method 2 — Manual install (script + bb-widget) — fallback only
Add the plugin script yourself and place widgets directly
Add your personalized Buying Buddy plugin script to the page yourself, then place <bb-widget> elements (directly in JSX on React 19, or through a small wrapper component on any React version). Use this method only if you genuinely cannot add the @buyingbuddy/react package to your project. It requires more care to get right and is not recommended for single-page apps or AI website builders, where the package is the reliable choice.
Using an AI Website Builder (HighLevel AI Studio & others)
If your site is built with an AI website builder such as HighLevel AI Studio — or a similar tool like Lovable, Bolt, or v0 — you are working in a standard React project (typically Vite + React + TypeScript). Use Method 1, the @buyingbuddy/react package: install it, wrap your app in the provider, and add widgets as components. Do not use the manual script method — it is unreliable in AI builders (see below).
Because these builders are driven by an AI assistant, the fastest way to set everything up is to hand that assistant a ready-made prompt. Your Buying Buddy dashboard generates this prompt pre-filled with your account's activation key and foundation-page paths, so the AI has everything it needs to install the package, create your Results and Details pages, add the disclaimer, and offer a search form.
Get your AI setup prompt: Go to Installation and Setup — — and open the Add Plugin tab. Under Set Up With AI, click “Show the AI prompt”, copy it, and paste it into your builder's AI chat. The prompt is personalized to your account.
HighLevel AI Studio: The AI prompt is the recommended primary method — let AI Studio's assistant do the installation for you. An older HTML-style install (a plugin <script> plus <bb-widget> elements) is unreliable in AI Studio: the script scans the page once on load, but your React widgets haven't rendered yet, and single-page navigation adds and removes them without the script re-scanning. The @buyingbuddy/react package solves this by hooking into React's component lifecycle, so widgets initialize exactly when they mount.
Authorize your preview domain: AI builders give you a preview/staging address (for example, a *.vibepreview.com URL in AI Studio) that is separate from your live domain. Widgets only load on authorized domains, so add your preview domain to your Buying Buddy account before previewing — otherwise widgets will not appear while you are still editing.
Setting up by hand, or using another React framework? Continue with the step-by-step instructions below.
Important Notes Before You Begin
Account & Activation Key: The widgets will not load without a valid Buying Buddy account. You will need your activation key from . There is no anonymous or "try it" mode. Don't have an account yet? Start a free trial or buy a license.
Domain Authorization: Buying Buddy widgets only load on domains registered to your account. On an unauthorized domain you will see a "Sorry, this domain is not authorized" message. Local addresses such as localhost are not currently supported, but you can use a sandbox, staging, or development domain — just contact support via the Help Desk to request that it be added as an authorized domain.
Client-Side Rendering & SEO: Buying Buddy widgets render in the browser after the runtime loads — their content is not server-rendered and is not present in the initial HTML.
React version: The package requires React 18 or 19 as a peer dependency. The manual method works on any modern React version.
How Buying Buddy Widgets Work in React
Buying Buddy widgets are implemented as HTML Custom Elements (<bb-widget>), registered by the plugin runtime. They use the standard Custom Elements lifecycle — specifically connectedCallback — so a widget initializes the moment its element is attached to the DOM, reading its attributes (data-type, data-filter, and so on) at that point.
In plain HTML this is seamless: the element is parsed with its attributes already present. In React, prior to React 19, unknown props on a custom element could be dropped or set as DOM properties instead of HTML attributes — so a widget could initialize without its configuration and render empty. The reliable pattern across all React versions is to create the <bb-widget> element imperatively and set its attributes with setAttribute before attaching it to the DOM.
Good news: The @buyingbuddy/react package does all of this for you internally. If you use Method 1, you never have to think about custom-element attribute handling — you just render a component.
Method 1 — The @buyingbuddy/react Package (Recommended)
1
Install the Package & Get Your Activation Key
Add the dependency and find your activation key
Inside an existing React project, install the package and make sure React is present:
npm install @buyingbuddy/react npm install react react-dom
Starting from scratch? This gets you from nothing to a running page with Vite:
npm create vite@latest my-site -- --template react cd my-site npm install npm install @buyingbuddy/react npm run dev
Finding Your Activation Key
You can locate your activation key in several places:
- Welcome email: If you just created an account, check the welcome email we sent you—your activation key is included there.
- Widgets page: Your activation key is displayed at the bottom of the dropdown menu in your Buying Buddy dashboard.
- Authorized Websites page: Navigate to to find your key at the top of the page.
- Plugin Installation page: Go to Installation and Setup — — where it is shown with your plugin install details.
Copy that value — you will pass it to the provider as the activationKey prop in the next step (Step 2).

2
Add the BuyingBuddyProvider
Wrap your app once, near the top of the tree
Add a single <BuyingBuddyProvider> near the top of your component tree. It loads the Buying Buddy runtime for your account once, waits for it to initialize, then renders your widgets. While it loads it shows the optional fallback. Expand the section for your framework below.
Use one provider per page. The Buying Buddy runtime installs a single global, so a page should use one provider with one activationKey. A widget rendered outside a provider will throw an error.
Vite / Create React App
Wrap <App /> in your entry file
Wrap your root component in src/main.jsx (Vite) or src/index.js (CRA):
// src/main.jsx
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { BuyingBuddyProvider } from "@buyingbuddy/react"
import App from "./App.jsx"
createRoot(document.getElementById("root")).render(
<StrictMode>
<BuyingBuddyProvider activationKey="your-activation-key">
<App />
</BuyingBuddyProvider>
</StrictMode>
)
Next.js (App Router)
A client provider wrapped around children in the root layout
The provider runs in the browser (it loads a script and reads window), so it must live in a Client Component. Create a small client wrapper, then use it in your server-rendered root layout.
// app/providers.tsx
"use client"
import { BuyingBuddyProvider } from "@buyingbuddy/react"
export default function Providers({ children }) {
return (
<BuyingBuddyProvider activationKey="your-activation-key">
{children}
</BuyingBuddyProvider>
)
}
// app/layout.tsx
import Providers from "./providers"
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
Next.js (Pages Router)
Wrap the app in _app.tsx
Wrap the application in your custom pages/_app.tsx:
// pages/_app.tsx
import { BuyingBuddyProvider } from "@buyingbuddy/react"
export default function App({ Component, pageProps }) {
return (
<BuyingBuddyProvider activationKey="your-activation-key">
<Component {...pageProps} />
</BuyingBuddyProvider>
)
}
Gatsby
Wrap the root element in gatsby-browser.js
Because the provider is client-only, wrap the root element in gatsby-browser.js:
// gatsby-browser.js
import React from "react"
import { BuyingBuddyProvider } from "@buyingbuddy/react"
export const wrapRootElement = ({ element }) => (
<BuyingBuddyProvider activationKey="your-activation-key">
{element}
</BuyingBuddyProvider>
)
Remix
Wrap the Outlet in app/root.tsx
Wrap the <Outlet /> in your app/root.tsx. The provider renders its fallback on the server and loads the runtime once in the browser:
// app/root.tsx
import { Outlet } from "@remix-run/react"
import { BuyingBuddyProvider } from "@buyingbuddy/react"
export default function App() {
return (
<BuyingBuddyProvider activationKey="your-activation-key">
<Outlet />
</BuyingBuddyProvider>
)
}
Tip: Pass a fallback prop to show a loading state while the runtime initializes, for example <BuyingBuddyProvider activationKey="..." fallback={<p>Loading…</p>}>. On a load failure the provider shows its own built-in error message in place of your widgets.
3
Create Foundation Pages
Required Results and Details routes
Important: Buying Buddy needs two dedicated "Foundation Pages" — one for search results, one for property details. The main page content of each foundation page must contain only one widget (the results or details widget). It is fine to also have the Disclaimer widget in the page footer and login/account widgets in the header. For a full explanation, see Understanding Foundation Pages.
Create two routes in your application, each rendering a single widget. The example uses Next.js App Router conventions, but the pattern is the same for any framework — the import lives wherever your route components live.
// Results route -> /listing-results
import { BuyingBuddyWidget } from "@buyingbuddy/react"
export default function ResultsPage() {
return <BuyingBuddyWidget type="SearchResults" />
}
// Details route -> /listing-details
import { BuyingBuddyWidget } from "@buyingbuddy/react"
export default function DetailsPage() {
return <BuyingBuddyWidget type="SearchDetails" />
}
Note: SearchResults and ListingResults are interchangeable — both render the same results widget. Likewise SearchDetails and ListingDetails.
Confirm Foundation Page Settings:
- Return to your Buying Buddy dashboard
- Go to
- Verify the page addresses match your route paths:
- Results: /listing-results
- Details: /listing-details - Update them if your routes use different paths

Important: Do not add a SearchForm or QuickSearch widget to the Results or Details routes — this will break widget behavior.
4
Add the Disclaimer Widget
Show the required MLS disclaimer site-wide
Add the Disclaimer widget to a component that renders on every page — typically your site footer or layout — so the required MLS disclaimer appears once, site-wide, instead of under every widget.
// Footer.jsx
import { BuyingBuddyWidget } from "@buyingbuddy/react"
export default function Footer() {
return (
<footer>
{/* ...your footer content... */}
<BuyingBuddyWidget type="Disclaimer" />
</footer>
)
}
5
Add a Search Form & Test
Place a search form and confirm the full flow works
Add a search form to any page other than your Results or Details routes — your home page is a good choice. The type prop maps to the widget's data-type, and filter maps to data-filter.
import { BuyingBuddyWidget } from "@buyingbuddy/react"
// Standard search form
<BuyingBuddyWidget type="SearchForm" />
// Compact one-line quick search
<BuyingBuddyWidget type="QuickSearch" filter="formType:simple1" />
// Featured gallery — 12 of the account's own listings
<BuyingBuddyWidget type="FeaturedGallery" filter="limit:12" />
// Interactive map with a fixed height
<BuyingBuddyWidget type="InteractiveMap" style={{ height: "600px" }} />
Next.js App Router note: Pages that render widgets are interactive client components. Either add "use client" to the top of the page, or keep the widget in a small client component imported into a server page — that keeps page-level data fetching on the server while the widget renders on the client.
Test the Full Flow:
- Run your app on an authorized domain (or your authorized dev domain)
- Submit a search from the search form — you should land on your Results route with listings
- Click a listing — you should land on your Details route showing that property
- Confirm the Disclaimer appears in your footer
Success: If search, results, details, and the disclaimer all work, your Buying Buddy integration is live. Continue to Enable Property Sharing and the optional enhancements below.
Method 2 — Manual Install (Script + bb-widget) — Fallback Only
Use Method 1 (the package) whenever you can. This manual method is a fallback for the rare project that cannot add an npm dependency. It is not recommended for single-page apps or AI website builders: the plugin script scans the DOM on load, but React widgets render afterward, and client-side navigation adds and removes them without the script re-scanning — the exact problem the @buyingbuddy/react package was built to solve.
If you do use this method, you will add your personalized plugin script to the page yourself, then place <bb-widget> elements. The overall sequence — plugin, foundation pages, disclaimer, search form, test — is the same as Method 1.
1
Switch Your Website Type to HTML & Get Your Plugin Script
Generate the personalized JavaScript plugin
For the manual method, set your website type to HTML so the dashboard generates the correct personalized plugin script (which already includes your activation key).
- In the top menu, go to Website Options
- Select the Website Settings tab, and in the header click the link to change the website type to HTML
- Open the Installation and Setup page — it will now show your personalized JavaScript plugin (including your activation key) along with the matching instructions
- Copy the plugin JavaScript from the Add Plugin tab
📷 Screenshot placeholder: Website Settings tab with the "change website type to HTML" link, and the resulting personalized plugin code on the install page.
2
Add the Plugin Script Site-Wide
Load it once, in the page head, before widgets mount
The plugin script must load in the <head>, site-wide, and must not be deferred or loaded asynchronously. Use the personalized script you copied in Step 1 (shown below as YOUR_BUYING_BUDDY_PLUGIN.js). Expand the section for your framework.
Vite / Create React App
Add the script tag to index.html
Add the script tag inside the <head> of your index.html:
<!-- index.html -->
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
<!-- Paste your personalized Buying Buddy plugin script here -->
<script src="https://www.mbb2.com/.../YOUR_BUYING_BUDDY_PLUGIN.js"></script>
</head>
Next.js (App Router)
Using next/script in the root layout
Add the script in your root app/layout.tsx using Next.js's Script component with strategy="beforeInteractive" so it runs before React hydrates:
// app/layout.tsx
import Script from "next/script"
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<Script
src="https://www.mbb2.com/.../YOUR_BUYING_BUDDY_PLUGIN.js"
strategy="beforeInteractive"
/>
</head>
<body>{children}</body>
</html>
)
}
Do not use strategy="lazyOnload" or strategy="afterInteractive". The plugin must be available before widgets mount — beforeInteractive is the correct strategy.
Next.js (Pages Router)
Using _document.tsx
Add the script tag inside the <Head> of your custom pages/_document.tsx:
// pages/_document.tsx
import { Html, Head, Main, NextScript } from "next/document"
export default function Document() {
return (
<Html lang="en">
<Head>
<script src="https://www.mbb2.com/.../YOUR_BUYING_BUDDY_PLUGIN.js" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
Gatsby
Using gatsby-ssr.js to inject into the head
In your project root, add or edit gatsby-ssr.js:
// gatsby-ssr.js
import React from "react"
export const onRenderBody = ({ setHeadComponents }) => {
setHeadComponents([
<script
key="buying-buddy-plugin"
src="https://www.mbb2.com/.../YOUR_BUYING_BUDDY_PLUGIN.js"
/>,
])
}
Remix
In the root route head
In your app/root.tsx, add the script to the <head> inside the Layout function:
// app/root.tsx
export function Layout({ children }) {
return (
<html lang="en">
<head>
<Meta />
<Links />
<script src="https://www.mbb2.com/.../YOUR_BUYING_BUDDY_PLUGIN.js" />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
Critical — all frameworks: Add the plugin script once only, site-wide. Never add it inside individual page components, and do not modify the plugin code.
3
Render Widgets
Place bb-widget elements in your JSX
With the plugin loaded, you can place widgets. There are two approaches depending on your React version.
React 19 — directly in JSX:
React 19 passes unknown props to custom elements as HTML attributes, so you can write the element directly:
<bb-widget data-type="SearchForm"></bb-widget> <bb-widget data-type="QuickSearch" data-filter="formType:simple1"></bb-widget>
All React versions — a small wrapper component:
For reliability across every React version, create this wrapper once and import it wherever you need a widget. It creates the <bb-widget> element imperatively and sets its attributes before attaching it to the DOM.
// BuyingBuddyWidget.jsx
import { useEffect, useRef } from "react"
export default function BuyingBuddyWidget({ widgetType, filter, style, className }) {
const containerRef = useRef(null)
useEffect(() => {
const container = containerRef.current
if (!container) return
// Clear any existing widget (handles re-renders and filter changes)
container.innerHTML = ""
// Create the element imperatively — attributes are set before
// DOM attachment, so connectedCallback sees them immediately.
const widget = document.createElement("bb-widget")
widget.setAttribute("data-type", widgetType)
if (filter) widget.setAttribute("data-filter", filter)
container.appendChild(widget)
return () => {
container.innerHTML = ""
}
}, [widgetType, filter])
return (
<div
ref={containerRef}
style={{ width: "100%", ...style }}
className={className}
suppressHydrationWarning
/>
)
}
Then use it like any other component:
import BuyingBuddyWidget from "./BuyingBuddyWidget" <BuyingBuddyWidget widgetType="SearchForm" /> <BuyingBuddyWidget widgetType="FeaturedList" filter="city:denver+price_min:300000" />
Note: This is the same technique the @buyingbuddy/react package uses internally — the package simply packages it (plus script loading) for you.
4
Foundation Pages, Disclaimer, Search Form & Test
Same sequence as the package method
From here, follow the same sequence as Method 1, using <bb-widget> (or your wrapper) instead of <BuyingBuddyWidget>:
- Foundation pages: create a Results route with
<bb-widget data-type="ListingResults">and a Details route with<bb-widget data-type="SearchDetails">, then confirm the slugs in - Disclaimer: add
<bb-widget data-type="Disclaimer">to your footer/layout - Search form: add
<bb-widget data-type="SearchForm">to a page other than Results or Details - Test: search → results → details, and confirm the disclaimer renders
<bb-widget data-type="ListingResults"></bb-widget> <bb-widget data-type="SearchDetails"></bb-widget> <bb-widget data-type="Disclaimer"></bb-widget> <bb-widget data-type="SearchForm"></bb-widget>
Widget Props Reference
When using the @buyingbuddy/react package, <BuyingBuddyWidget> accepts the following props. Only type is required.
| Prop | Type | Description |
|---|---|---|
type | string | Required. The widget to render, e.g. "SearchForm", "FeaturedGallery", "InteractiveMap". |
filter | string | Property filter as +-joined tokens. See the Filter Parameters Reference. |
style | object | Inline styles for the wrapper. Useful for sizing widgets such as InteractiveMap. |
className | string | CSS class for the wrapper element. |
id | string | HTML id attribute for the widget. |
onWidgetLoaded | function | Callback fired when the widget finishes loading its content. |
The provider, <BuyingBuddyProvider>, accepts activationKey (required) and an optional fallback rendered while the runtime loads.
Available Widget Types for REACT
The table below lists the Buying Buddy widgets in their React form. Pass the value shown to the type prop of <BuyingBuddyWidget> (inside your <BuyingBuddyProvider>). This mirrors the master Published IDX Widget Set — refer to that article for the definitive list.
| Widget | React component |
|---|---|
| Search Form | <BuyingBuddyWidget type="SearchForm"/> |
| Results | <BuyingBuddyWidget type="ListingResults"/> |
| Property Details | <BuyingBuddyWidget type="SearchDetails"/> |
| List of Properties | <BuyingBuddyWidget type="FeaturedList"/> |
| Gallery Display | <BuyingBuddyWidget type="FeaturedGallery"/> |
| Interactive Map | <BuyingBuddyWidget type="InteractiveMap"/> |
| Communities | <BuyingBuddyWidget type="Communities"/> |
| Quick Search | <BuyingBuddyWidget type="QuickSearch"/> |
| Login Panel | <BuyingBuddyWidget type="LoginPanel"/> |
| Lead Capture Form | <BuyingBuddyWidget type="LcForm"/> |
| Market Stats | <BuyingBuddyWidget type="MarketStats"/> |
| Agent / Roster | <BuyingBuddyWidget type="Brokers"/> |
| Agent / Roster (permalinks) | <BuyingBuddyWidget type="OfficeRoster"/> |
| Disclaimer | <BuyingBuddyWidget type="Disclaimer"/> |
| Calculator | <BuyingBuddyWidget type="Calculator"/> |
Note: SearchResults and ListingResults are interchangeable, as are SearchDetails and ListingDetails — each pair renders the same widget. When using the manual method (Method 2), use the same names as the data-type attribute, e.g. <bb-widget data-type="FeaturedGallery"></bb-widget>.
Enable Property Sharing
Set up social media sharing so shared property links show the correct listing photo and details.
For React/single-page apps: Because listing content is rendered client-side, social scrapers won't read per-listing meta tags from your routes. The domain-level setup below ensures shared links resolve to correctly rendered share previews.
To allow visitors to share properties from your website on Facebook and other social media platforms with the correct listing photos and details, you'll need to set up a "bb" subdomain for your domain.
Setup Options: You can use either Cloudflare (recommended for free SSL certificates) or your current domain registrar.
The Social Media Sharing Setup Instructions will guide you through both options.
Next Step:
Note: Your Buying Buddy dashboard has customized instructions for your domain in the Installation and Setup section (Social Share Settings tab) of your Buying Buddy account.Widgets - Installation and Setup : Social Share tab
Troubleshooting
Widgets render an empty container
The most likely cause is that the runtime has not registered the <bb-widget> custom element before the widget mounts. With the package, make sure your widget is inside <BuyingBuddyProvider>. With the manual method, confirm the plugin script is in the <head> and not deferred. In the browser console, run customElements.get("bb-widget") — if it returns undefined, the runtime has not registered.
"Invalid activation key or unauthorized domain"
The package provider shows this built-in error when the runtime loads but the account/domain check fails. Verify your activationKey is correct and that the current domain is registered to your account. For development, contact support via the Help Desk to authorize your sandbox or staging domain.
"Sorry, this domain is not authorized"
The runtime is loading but the domain is not registered in your Buying Buddy account. For production, verify the domain in . For development domains (a sandbox or staging domain), contact support to authorize them.
Hydration errors in Next.js
A hydration mismatch can occur because the widget DOM is created client-side and isn't present in the server-rendered HTML. The package's wrapper already sets suppressHydrationWarning on its container. If you wrote your own wrapper (manual method), add suppressHydrationWarning to the wrapper <div> as shown in Method 2, Step 3.
Widget loses state when a prop changes
Changing type or filter tears down and rebuilds the underlying widget, which loses in-widget state such as map position, scroll, or form input. This is expected. Avoid changing those props unnecessarily; if parent re-renders are the cause, memoize the props or wrap the widget in React.memo.
Plugin loads more than once
Loading the runtime more than once causes unpredictable behavior. With the package, use a single provider per page. With the manual method, ensure the script appears only in your root layout, _document, or index.html — never inside page components. Search your codebase for the plugin URL to confirm there is only one instance.
"SQL Error" on the Details page during testing
Viewing the Details route directly, without a property reference, can show a temporary error. This is normal during testing and is never seen by visitors who arrive from a results listing.
Next Steps
Your React site is ready for advanced Buying Buddy features. Consider adding:
- Featured listing galleries for your own properties
- Neighborhood / community pages
- Lead capture forms connected to the Buying Buddy CRM
- An interactive map search experience
- Custom styling with Widget Themes
Related Resources
- Install Buying Buddy on Framer — dedicated guide for Framer
- Understanding Foundation Pages
- Filter Parameters Reference
- Widget Overview
- Troubleshooting JavaScript Plugin Installation Issues
Need additional help? Contact our support team through your Buying Buddy dashboard.