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

PlatformApproach
HTML / any websiteLoads widget.js
Next.jsLoads widget.js
React (Vite / CRA)Loads widget.js
AngularLoads widget.js
WordPressLoads widget.js
ShopifyLoads widget.js
React NativeWebView
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-webview

Render 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.