Embed the widget
One script tag adds the chat bubble to your site. It loads in the background, so it won’t slow your pages down, and it works with any site or framework.
Script tag
Add this to your HTML, just before the closing </body> tag:
<script
src="https://cdn.bubblegram.co/widget.js"
data-key="pk_live_YOUR_KEY_HERE"
async
></script>
You’ll find the snippet with your key already filled in under Widget → Install in the dashboard. Copy it from there.
Using an AI coding tool like Claude Code or Cursor? Click Copy prompt next to Install with AI and paste it in. It has your key and the right steps for your framework.
Attributes
| Attribute | Required | Description |
|---|---|---|
data-key | Required | Your project’s public API key. Starts with pk_live_. |
data-api | Optional | API base URL. Defaults to https://api.bubblegram.co. |
data-email | Optional | Pre-fill the customer’s email. Useful on server-rendered pages where the user is already signed in. Skips the email input even when “Require email” is enabled. |
JavaScript API
After the script loads, a Bubblegram object is available on window. You can control the widget programmatically:
Bubblegram.open() // open the chat panel
Bubblegram.close() // close the chat panel
Bubblegram.connect() // reconnect WebSocket (if disconnected)
Bubblegram.setEmail('[email protected]') // pre-fill email for signed-in users
The widget has no way to detect your app’s auth state on its own. Without data-email or setEmail, every customer shows up as anonymous in your dashboard, signed in or not. Call setEmail yourself once the email is known after client-side auth. For example, in React or Next.js after the session loads:
useEffect(() => {
if (session?.user?.email) {
window.Bubblegram?.setEmail(session.user.email)
}
}, [session?.user?.email])
If this runs before widget.js has finished loading (common in SPAs, since the script tag is async), window.Bubblegram won’t exist yet and the call is silently lost. Use the queue stub shown in the React/Next.js examples below to make it safe to call anytime.
Calling setEmail with a different email than the one already set starts a fresh conversation: the current session is cleared and reinitialized, local message history is wiped, and the WebSocket reconnects, before the new email is saved. The first setEmail call for a customer (no email set yet) doesn’t trigger this. Keep this in mind if you call setEmail again later, e.g. when a customer switches accounts, since any messages from the old session won’t carry over.
On server-rendered pages (PHP, Rails, Django, etc.) where the email is available at render time, use the data-email attribute instead:
<script
src="https://cdn.bubblegram.co/widget.js"
data-key="pk_live_YOUR_KEY_HERE"
data-email="<?= $currentUser->email ?>"
async
></script>
For React, Next.js, or other SPAs, add the script to your root HTML file. In Vite or Create React App, that’s index.html:
<script>
window.Bubblegram = window.Bubblegram || (function () {
var q = []
var api = { q: q }
;['init', 'sendMessage', 'connect', 'open', 'close', 'setEmail', 'destroy'].forEach(function (m) {
api[m] = function () { q.push([m, Array.prototype.slice.call(arguments)]) }
})
return api
})()
</script>
<script
src="https://cdn.bubblegram.co/widget.js"
data-key="pk_live_YOUR_KEY_HERE"
async
></script>
In Next.js, use the built-in Script component in your _app.tsx or root layout. Load the stub with beforeInteractive so it exists before any of your components try to call Bubblegram:
import Script from 'next/script'
<Script id="bubblegram-stub" strategy="beforeInteractive">
{`window.Bubblegram = window.Bubblegram || (function () {
var q = []
var api = { q: q }
;['init', 'sendMessage', 'connect', 'open', 'close', 'setEmail', 'destroy'].forEach(function (m) {
api[m] = function () { q.push([m, Array.prototype.slice.call(arguments)]) }
})
return api
})()`}
</Script>
<Script
src="https://cdn.bubblegram.co/widget.js"
data-key="pk_live_YOUR_KEY_HERE"
strategy="afterInteractive"
/> Attachments
Customers can attach a file to any widget message: an image, a PDF, or a short video. It shows up in the conversation in your inbox and Telegram. To send a file back, attach it to your reply in Telegram.
Limits
| Type | Accepted formats | Max size |
|---|---|---|
| Image | JPEG, PNG, WebP, GIF, HEIC/HEIF | 5 MB |
| application/pdf | 15 MB | |
| Video | MP4, WebM, QuickTime | 20 MB |
HEIC/HEIF photos (the default on iPhone) are converted automatically so they open in any browser. The video limit matches Telegram’s own file-fetch limit for bots, so it isn’t something we can raise on our end.
Starter and up. Attachments are available on Starter, Pro, and Custom. Turn on Allow attachments under Widget and publish. On Free, customers can’t attach files.