Frameworks & platforms
Minaya is a single script tag, so it works on any website without a framework integration. These are the per-platform specifics — where the snippet goes, and what to watch out for.
Which approach applies to you
| Platform | Approach |
|---|---|
| HTML / any website | Loads widget.js |
| Next.js | Loads widget.js |
| React (Vite / CRA) | Loads widget.js |
| Angular | Loads widget.js |
| WordPress | Loads widget.js |
| Shopify | Loads widget.js |
| React Native | WebView |
| Android (Kotlin) | WebView or REST API |
| iOS (Swift) | WebView or REST API |
Browser platforms all load the same widget.js. Native mobile has no DOM, so those platforms either host the widget in a WebView or call the public REST API and render the conversation in their own UI.
HTML / any website
One script tag before the closing body tag.
Paste the snippet
Add this just before </body> on every page that should show the widget.
<script
src="https://widget.minaya.ai/widget.js"
data-site-key="your-site-key"
data-api-url="https://api.minaya.ai"
async
></script>Next.js
Use next/script so the widget loads after the page is interactive.
Add it to the root layout
App Router — app/layout.tsx. The lazyOnload strategy keeps the widget off the critical path.
import Script from "next/script";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://widget.minaya.ai/widget.js"
data-site-key="your-site-key"
data-api-url="https://api.minaya.ai"
strategy="lazyOnload"
/>
</body>
</html>
);
}Pages Router alternative
If you are on pages/_app.tsx, render the same <Script> component there instead.
Note. Do not put the script in next/head — Next strips script tags from it. Use next/script.
React (Vite / CRA)
Add the tag to index.html, or inject it from a hook.
Simplest — index.html
Paste the snippet before </body> in your index.html. Nothing else to do.
<script
src="https://widget.minaya.ai/widget.js"
data-site-key="your-site-key"
data-api-url="https://api.minaya.ai"
async
></script>Or mount it from a component
Useful when you only want the widget on certain routes. The cleanup removes the tag on unmount.
import { useEffect } from "react";
function MinayaWidget() {
useEffect(() => {
const script = document.createElement("script");
script.src = "https://widget.minaya.ai/widget.js";
script.dataset.siteKey = "your-site-key";
script.dataset.apiUrl = "https://api.minaya.ai";
script.async = true;
document.body.appendChild(script);
return () => {
script.remove();
};
}, []);
return null;
}Angular
Add the tag to index.html, or inject it from a component.
Simplest — src/index.html
Paste the snippet before </body>.
<script
src="https://widget.minaya.ai/widget.js"
data-site-key="your-site-key"
data-api-url="https://api.minaya.ai"
async
></script>Or load it from a component
Renderer2 keeps the DOM work compatible with server-side rendering.
import { Component, OnInit, Renderer2, Inject } from "@angular/core";
import { DOCUMENT } from "@angular/common";
@Component({ selector: "app-minaya", template: "" })
export class MinayaComponent implements OnInit {
constructor(
private renderer: Renderer2,
@Inject(DOCUMENT) private document: Document,
) {}
ngOnInit(): void {
const script = this.renderer.createElement("script");
script.src = "https://widget.minaya.ai/widget.js";
script.setAttribute("data-site-key", "your-site-key");
script.setAttribute("data-api-url", "https://api.minaya.ai");
script.async = true;
this.renderer.appendChild(this.document.body, script);
}
}WordPress
Paste into the theme footer, or use a header/footer scripts plugin.
Option A — a plugin (recommended)
Install WPCode or Insert Headers and Footers, open its settings, and paste the snippet into the Footer box. This survives theme updates.
<script
src="https://widget.minaya.ai/widget.js"
data-site-key="your-site-key"
data-api-url="https://api.minaya.ai"
async
></script>Option B — edit the theme
Appearance → Theme File Editor → footer.php. Paste immediately before </body>. Use a child theme, or an update will overwrite it.
Verify
Open your site in a private window. The launcher appears bottom-right within a second or two.
Note. Works with Elementor, Divi and other page builders — it is plain JavaScript, not a plugin integration.
Shopify
Add to theme.liquid before the closing body tag.
Open the theme editor
Online Store → Themes → ... → Edit code.
Edit theme.liquid
Under Layout, open theme.liquid and paste the snippet immediately before </body>. Save.
<script
src="https://widget.minaya.ai/widget.js"
data-site-key="your-site-key"
data-api-url="https://api.minaya.ai"
async
></script>Allow your store domain
In Minaya, add both your myshopify.com domain and your custom domain to Allowed origins.
Note. Shopify blocks third-party scripts on checkout pages unless you are on Shopify Plus, so the widget will not appear during checkout.
React Native
There is no DOM in React Native, so host the widget in a WebView.
Install react-native-webview
npm install react-native-webview, then run pod install for iOS.
npm install react-native-webviewRender the widget in a WebView
The widget opens automatically so the visitor does not have to find a launcher inside the WebView.
import { WebView } from "react-native-webview";
const html = `<!doctype html>
<html>
<head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
<body>
<script
src="https://widget.minaya.ai/widget.js"
data-site-key="your-site-key"
data-api-url="https://api.minaya.ai"
></script>
</body>
</html>`;
function SupportChat() {
return (
<WebView
originWhitelist={["*"]}
source={{ html, baseUrl: "https://api.minaya.ai" }}
/>
);
}Turn on Open automatically
In Widget appearance, enable Open automatically so the panel is already open inside the WebView.
Note. baseUrl must be set, or the request origin is null and the site key check rejects it. Add that origin to Allowed origins.
Android (Kotlin)
Either host the widget in a WebView, or call the REST API and use your own UI.
Option A — WebView
Fastest path. JavaScript must be enabled, or the widget will not run.
val webView = findViewById<WebView>(R.id.webView)
webView.settings.javaScriptEnabled = true
val html = """
<!doctype html>
<html>
<head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
<body>
<script src="https://widget.minaya.ai/widget.js"
data-site-key="your-site-key"
data-api-url="https://api.minaya.ai"></script>
</body>
</html>
""".trimIndent()
webView.loadDataWithBaseURL("https://api.minaya.ai", html, "text/html", "UTF-8", null)Option B — native UI over the REST API
Build the chat in Compose or Views and call the API directly. Keep the returned sessionId and send it back to continue a conversation.
data class ChatRequest(val message: String, val sessionId: String? = null)
data class ChatResponse(val sessionId: String, val reply: String)
// Retrofit setup
val api = Retrofit.Builder()
.baseUrl("https://api.minaya.ai/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(MinayaApi::class.java)
val response = api.chat("your-site-key", ChatRequest("What are your hours?"))Note. Your site key ships inside the app binary, so treat it as public. Allowed origins do not protect native traffic — rely on the per-plan message limits.
iOS (Swift)
Either host the widget in a WKWebView, or call the REST API and use your own UI.
Option A — WKWebView
Loads the same widget your website uses.
import WebKit
let webView = WKWebView(frame: view.bounds)
view.addSubview(webView)
let html = """
<!doctype html>
<html>
<head><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
<body>
<script src="https://widget.minaya.ai/widget.js"
data-site-key="your-site-key"
data-api-url="https://api.minaya.ai"></script>
</body>
</html>
"""
webView.loadHTMLString(html, baseURL: URL(string: "https://api.minaya.ai"))Option B — native UI over the REST API
Build the chat in SwiftUI and call the API directly. Store sessionId to continue the conversation across messages.
struct ChatResponse: Decodable {
let sessionId: String
let reply: String
}
func sendMessage(_ text: String, sessionId: String?) async throws -> ChatResponse {
var request = URLRequest(url: URL(string: "https://api.minaya.ai/public/chat")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("your-site-key", forHTTPHeaderField: "X-Site-Key")
var body: [String: Any] = ["message": text]
if let sessionId { body["sessionId"] = sessionId }
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(ChatResponse.self, from: data)
}Note. Your site key ships inside the app binary, so treat it as public. Allowed origins do not protect native traffic — rely on the per-plan message limits.
Troubleshooting
The widget does not appear
Open the browser console. A missing data-site-key or data-api-url logs an explicit error. If the console is clean, check that the script tag is inside <body> and not blocked by a content security policy.
403 on every request
The requesting origin is not on the widget’s allowlist. Add the exact origin — scheme included — under Allowed origins. A WebView with no baseUrl sends a null origin, which never matches.
Two widgets on the page
The script is included twice. Minaya ignores the second copy, so you should see only one launcher; if you see two, one of them is a different chat product.