GUIDES

Embed the widget

One script tag puts an agent inside your product. It reads your docs, calls your tools, and does the thing, right where your users already are.

The widget is vanilla JavaScript with zero dependencies. It renders in a Shadow DOM so it never collides with your styles. Paste one script tag, set your project key, and a floating chat button appears. That is the whole install.

Add the script

Grab your project key from the admin dashboard (it starts with era_) and drop the tag anywhere in your page, ideally right before the closing </body>. It paints a floating launcher button as soon as it runs; the panel stays closed until someone clicks it.

html
<script
  src="https://agentifys.ai/static/widget.js"
  data-api-key="era_your_project_key"
  data-title="Assistant"
  data-primary-color="#6366f1"
  data-placeholder="Ask me anything…"
  data-position="bottom-right"
></script>
Only data-api-key is required. Everything else has a default, so the shortest working install is a single tag with just your key.

Configuration attributes

Configure the widget entirely from data-* attributes on the script tag. No build step, no config file. Each one is read once, when the script runs.

FieldTypeDescription
data-api-keyrequiredstringYour project key. Starts with era_. Ties the widget to one project and its tools, knowledge, and keys. Without it nothing renders.
data-titlestringHeader text shown at the top of the panel. Defaults to "AI Assistant".
data-primary-colorhexAccent color for the launcher and buttons. Defaults to #6366f1. Set your brand colour here rather than only in CSS — the widget bakes these channels into its glows at boot.
data-placeholderstringPlaceholder text in the message input. Defaults to "Ask me anything…".
data-positionstringLauncher corner: bottom-right (default) or bottom-left.
data-themestringForce a light or dark panel. Defaults to "dark"; set to "light" to switch. It does not follow the host page.
data-max-lengthnumberCharacter cap on a single user message. Defaults to 4000.
data-feedbackstringThumbs up / down on answers. On by default; set to "off" to hide the controls.
data-optionsstringClickable choice buttons the agent can render. On by default; set to "off" to disable them.
data-status-stylestringTone of the live tool-status lines. Defaults to "professional"; the other value is "playful".
data-base-urlurlSend requests to your own proxy instead of the origin the script came from. Advanced, and it does not hide your project key — the key is in the page either way.
data-point-at"off"The agent spotlighting elements on your page. On by default; set to "off" to withdraw it. Your own ERA.point() calls still work.
data-select-ask"on"Opt in to the "Ask AI" pill on a text selection. Off by default.
data-dictation"off"Microphone that dictates into the composer. On by default where the browser supports it; set to "off" to hide it.
data-read-aloud"off"Toggle that reads answers aloud. On by default where the browser supports it; set to "off" to hide it.
data-command-keystringOpt in to the command palette, e.g. "k" for Ctrl/Cmd+K. Off unless set.

Full descriptions, the CSS-variable and ::part() theming contract, and a live playground are in the Widget API reference.

Identify the current user

Set window.ERAConfig before the widget script loads so the agent knows who it is talking to. The id is the only required field. Anything you put under data becomes context the agent (and your policies) can use.

html
<script>
  window.ERAConfig = {
    user: {
      id: "user_123",           // required
      name: "Sara",
      email: "sara@acme.com",
      role: "admin",
      data: { plan: "pro", seats: 12 }
    }
  }
</script>
<script src="https://agentifys.ai/static/widget.js" data-api-key="era_your_project_key"></script>
This is open identity. It is perfect for personalization but the browser could spoof it, so it does not unlock private uploads or the vault. To make identity tamper-proof, sign it server-side. See Identify users.
Set it before the widget tag, as shown. On boot the widget asks the server which per-user features this project may show, and it skips that call entirely when there is no user.id yet — so an identity assigned later still personalizes replies, but the attach button, the My-tasks button and the "Connect your account" prompts stay hidden. If your app only knows the user after the widget has loaded, inject the tag then instead (see below).

After boot the widget re-reads window.ERAConfig.user on every request it makes, so assigning a new object switches identity on a running widget without a reload. That is also how you keep a signed identity alive: a signature expires 300 seconds after it was made, so on a page that stays open, re-sign on a timer and assign the result. The pattern is in the Widget API reference.

Drive it from JavaScript

Once loaded, the widget exposes a small global ERA object. Use it to open the panel from your own button, push a message in, or react to what the agent says.

js
// Open or close the panel from your own UI
ERA.open()
ERA.close()

// Send a message as if the user typed it
ERA.sendMessage("What is my current plan?")

// End this conversation and start a new one
ERA.clear()

// Open the "My tasks" panel (scheduled tasks)
ERA.openTasks()

// Kick off per-user MCP OAuth for one server
ERA.connectMcp("tavily")
ERA only exists after the widget script has loaded. If you call it from your own inline script, wait for the load event or place your code after the widget tag. After ERA.destroy() it is gone again, so use window.ERA?.method() in code that can run either side of a teardown.
Sign-out is the case that bites. The session id lives in localStorage keyed by project, not by user, so the next person to use that browser inherits it. With signed identity the server rejects the mismatch and every message they send comes back "Session not found." until the stored id is dropped; with open identity they quietly resume the previous user's conversation. ERA.clear() is what prevents both.

Every method, the full onMessage event shape, and what the agent can and cannot do to your page are in the Widget API reference.

Single-page apps

The widget is a classic script, not a module. It reads its configuration from document.currentScript, which your bundler cannot give it: an import of widget.js, or a tag with type="module", leaves that null. The widget then looks at the last <script> on the page, finds no data-api-key, logs [Agentifys Widget] data-api-key is required to the console and renders nothing at all.

So inject a real script element. Do it after you know who the user is, and tear it down when they sign out:

js
// Mount — after your app knows the user.
function mountEra(user) {
  window.ERAConfig = { user }
  const s = document.createElement("script")
  s.id = "era-widget-script"
  s.src = "https://agentifys.ai/static/widget.js"
  s.dataset.apiKey = "era_your_project_key"
  s.dataset.title = "Assistant"
  document.body.appendChild(s)
}

// Unmount — on sign-out, or when your root component goes away.
function unmountEra() {
  window.ERA?.clear()      // drop the stored session id
  window.ERA?.destroy()    // remove the widget and its document listeners
  document.getElementById("era-widget-script")?.remove()
}
Mounting twice is safe. The script checks for a previous instance and destroys it before building a new one, so a remount never doubles up the widget or its listeners. What is not cleaned up on its own is a host element you delete yourself: removing #era-widget from the DOM leaves the listeners the widget put on your document — a keydown handler always, plus mouseup, keyup and mousedown when data-select-ask is on — each holding a detached tree alive. ERA.destroy() is the only thing that takes them off.