aon.js Web Plugin
aon.js is the script that puts a Content Assistant placement on a web
page. Add it once per page and it reads the placement's styles from AON,
draws them, opens the chat panel, and attributes everything to the
placement. This page is the reference for the tag, the DOM it expects and
creates, the requests it makes, and the window.AON API. For the
step-by-step setup, read the
Content Assistant guide first.
The tag
<script
src="https://plugin.aon.pro/aon.js"
data-placement-id="plc_your_placement_id"
data-lang="en"
referrerpolicy="origin"
async
></script>Put it once on every page, before </body>, normally in the shared layout.
A second copy on the same page does nothing.
| Attribute | Required | Value |
|---|---|---|
src | yes | https://plugin.aon.pro/aon.js |
data-placement-id | yes | The Content Assistant placement id from the Developer Portal, [A-Za-z0-9_-], 1–64 characters. Also the attribution key: offers shown by this assistant are credited to the placement's application. |
data-lang | no | Language of the panel's own text (header, buttons, prompts). BCP 47 tag, matched case-insensitively. See Panel languages. Default en. It never follows the visitor's browser. |
referrerpolicy | yes | origin. Keeps the full page URL (path and query) out of the script request when the page sends a permissive Referrer-Policy. |
async | recommended | The script never blocks rendering; it does all its work after load. |
data-prewarm | no | off (default), idle or eager. Whether to build the hidden chat panel before the first click. idle and eager download the panel (about 40 KB gzipped) on every visit unless the visitor has asked for Save-Data; use them on pages with a high open rate. |
data-publisher-id | no | An identifier of your choice. It only namespaces the device id stored on your origin (see Requests) and is never sent to AON. |
data-debug | no | Any value except false turns on the panel's diagnostic logging. |
data-prewarm values are case-sensitive. An invalid data-placement-id or
data-prewarm is ignored with one console.debug line and never replaces an
earlier valid value.
If document.currentScript is not available to the script (bundled, loaded
as a module, or inlined by a tag manager), the attributes cannot be read.
Then load it with a classic non-async tag and call
AON.init({ placementId: "…" }) synchronously right after it. The placement
id is fixed when the document finishes parsing (DOMContentLoaded), or
earlier if the panel is opened before that; a later init only logs a
debug line.
What the script draws
The placement's styles are switched on and off in the Developer Portal. The script fetches that setup on load and, once the document has parsed, draws what is on:
| Style | Drawn as | Needs in your HTML |
|---|---|---|
| Floating button | A fixed button in the viewport corner (button[data-aon-launcher], aria-label="Open assistant"), with an optional prompt bubble when "Show a prompt message" is on. A click opens the panel with the first suggested question for the page, or an empty panel when AON has not read the page. | Nothing. |
| End of content | Up to five suggested questions, plus a question box when "Include a question box" is on, rendered inside your spot as section[aria-label="Assistant suggestions"]. | One <div data-aon-slot="end-of-content"></div> on every page, right after its main content. The script looks for it once, when the document has parsed and the placement setup has arrived, and does not watch for later insertions; put it in the server-rendered or static HTML so it is there in time. |
| Inline mention, Inline card | Not drawn by the web script yet. Switching them on has no effect on web. | Nothing. |
The spot renders nothing until AON has read the page (see
Requests). Leave the div empty with no fixed height; the
script appends its own nodes and removes them again when the page's reading
is no longer valid. One spot per page: only the first is used.
On every page the script also adds a page-level div[data-aon-chat] shell
that holds the chat panel and a one-line disclosure, "Sponsored results by
agent offer network (AON)", linking to aon.pro with
rel="sponsored nofollow noopener". The panel itself is a cross-origin
<iframe title="AON"> from https://plugin.aon.pro, sandboxed
(allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox,
no top navigation) with allow="web-share; clipboard-write" and
referrerpolicy="origin". Everything the assistant says, every offer and
every price lives inside that iframe; the host page never receives offer
data.
Page requirements
- HTTPS. On an
http://page the script still loads and the floating button still opens the panel, but AON never reads the page, so the End of content spot stays empty and the panel opens without suggestions. - Own domain. AON reads a page by fetching its public URL. Pages behind a
login or on shared preview domains (
*.vercel.app,*.netlify.app,*.pages.dev,*.github.io, tunnels) are not read. - Single-page apps. AON reads a page on a full load. After a client-side
route change (
pushState,replaceState,popstate) the script hides the suggestions it drew for the previous URL and does not read the new one until the next full load (or untilinit({ lang })changes the locale, which reads the current URL). The chat panel keeps working.
Content-Security-Policy
If the page sends one, allow:
script-src https://plugin.aon.pro
frame-src https://plugin.aon.pro
connect-src https://plugin.aon.pro https://api.aon.proNo style-src change is needed: styles are set property by property, never
through injected <style> elements or style attributes.
Requests
The script makes three kinds of requests. None carries page text, query strings, hashes or anything read from the DOM.
| Request | When | Sends | Notes |
|---|---|---|---|
GET https://api.aon.pro/v1/public/placements/{placement_id}/setup | Once per page load, after the placement id is fixed. No credentials. | The placement id in the path | Returns type, status and content_assistant_setup (styles, with_prompt, with_ask_box). A valid response is stored in localStorage as aon:setup:<placement_id>. With a cached copy, the cached setup controls the current load and the fresh response only updates the next one, so a portal change shows on the second load. 404, a status other than live or a type other than content_assistant means there is no setup: an uncached load draws nothing, and the cache is cleared so the next load draws nothing either. |
POST https://plugin.aon.pro/api/page-contexts/resolve | On HTTPS pages: on load, again if init({ lang }) changes the locale or the page is restored from the back-forward cache; polled while the answer is PENDING. No credentials. | submitted_url = origin + pathname; semantic_context.locale when the language tag is valid | This is how AON reads the page. READY carries one to five suggested questions and the prompt text; anything else, including network errors and 403/429/503, degrades to "no suggestions" without touching the host page. Never sent on HTTP pages. |
https://plugin.aon.pro/panel/… | When the panel is first opened, or earlier with data-prewarm | The placement id (aon_pid) on the iframe URL; language, theme and your input go to the panel by postMessage | An ordinary cross-origin document, so it may carry plugin.aon.pro cookies. It talks to its own backend; every offer query it makes carries X-AON-Placement-Id with your placement id. |
Two localStorage keys may be written on your origin: aon:setup:<placement_id>
(above) and aon:did:<publisher_id> (a random, persistent device identity
created the first time the panel connects). Neither contains data from your
page.
JavaScript API
window.AON has exactly four keys: version, init, placement and
chat. version, placement and chat are read-only, and placement and
chat are frozen objects. Methods never throw: init and placement ignore
invalid values, chat.open with non-object options behaves as open() and
warns, and invalid input fields come back as invalid in the result
event.
AON.version
The script's version string.
AON.init(config)
Publisher-level configuration. Every field is optional and a missing field
keeps its current value; the tag's data-* attributes go through the same
call before window.AON exists.
| Field | Type | Effect |
|---|---|---|
placementId | string | Same as data-placement-id. Honoured only before the id is fixed (see The tag). |
lang | string | Same as data-lang. A new value re-reads the page for that locale and applies to the next panel session; an open panel keeps its language. |
prewarm | "off" | "idle" | "eager" | Same as data-prewarm. |
publisherId | string | Same as data-publisher-id. Takes effect only before the panel first connects. |
debug | boolean | Same as data-debug. |
AON.placement
Manual control of the two web styles. These calls always work, whatever the portal switches say, and they take precedence: once you have called one of them on a page, the script no longer draws that style automatically there. Each returns a function that removes what the call drew and nothing else.
// Floating button: "icon" alone, or "prompt" = icon plus the suggestion bubble.
const removeButton = AON.placement.floating("prompt");
// End of content, into a container you own. "ask" = questions + text box, "buttons" = questions only.
const removeSpot = AON.placement.endOfContent({
container: document.querySelector('[data-aon-slot="end-of-content"]'),
variant: "ask",
});floating keeps at most one button on the page; a second valid call replaces
the first. endOfContent draws only when AON has read the page and clears
itself when that reading stops being valid.
AON.chat
Direct control of the chat panel, for sites that build their own entry points. The placement styles use these same methods.
| Method | Returns | Behaviour |
|---|---|---|
open(options?) | input id or undefined | Shows the panel. With options.input the message is submitted in the same call. options.presentation sets lang and a theme (accent, radius) for this open. options.awaitInput: true opens a "preparing" state that waits for a following push. |
push(input) | input id | Submits host input without changing visibility. Before the first open it is buffered and sent when the panel opens. In a visible conversation it appears as a tappable suggestion rather than being sent. In a panel hidden by close() it is dropped (result with reason hidden). |
close() | — | Hides the panel and keeps the conversation. Submissions still in flight are dropped without a result. |
destroy() | — | Removes the iframe and the conversation, and clears anything buffered before the first open. Event subscriptions survive; a later open starts fresh. The div[data-aon-chat] shell stays. |
isOpen() | boolean | Whether the panel is visible right now. |
on(event, handler) | unsubscribe function | Events: opened, closed ({ reason: "host-close" | "visitor-close" | "destroy" }), result ({ inputId, results: [{ field, outcome, reason? }] }, at most one per submitted input). Handlers run asynchronously; one throwing handler does not affect the others. |
input is a plain object:
| Field | Type | Meaning |
|---|---|---|
message | string | The visitor message to submit, trimmed, up to 200 Unicode code points. This is the only field the assistant reads. |
refs.page | { url } | null | Override the page URL AON reads. The script already uses origin + pathname; set this only for hash or query routing, a canonical URL, or null to opt a page out. |
refs.category | { id } | null | A canonical AON taxonomy id to scope offers. Accepted but not applied yet (outcome: "dropped"). |
refs.offer | { id } | null | Reserved (outcome: "unsupported"). |
attribution | { placementId?, trigger? } | Analytics labels only (trigger: click, auto, api). Offer attribution comes from the tag's placement id, not from here. |
const inputId = AON.chat.open({
input: { message: "Which of these laptops is best for video editing?" },
});
AON.chat.on("result", ({ inputId: id, results }) => {
if (id === inputId) console.log(results);
});Panel languages
data-lang, init({ lang }) and presentation.lang accept the same set,
case-insensitively. For Chinese, a Hant script subtag or a TW, HK or
MO region selects Traditional; anything else is Simplified. Other tags are
matched on their primary subtag (pt-BR → pt); an unknown tag falls back
to English. The assistant's answers follow the language the visitor
writes in, not this setting.
| Tag | Language |
|---|---|
en | English |
zh-CN, zh-Hans, zh | Simplified Chinese |
zh-TW, zh-Hant | Traditional Chinese |
ja | Japanese |
ko | Korean |
id | Indonesian |
ms | Malay |
th | Thai |
vi | Vietnamese |
fil, tl | Filipino |
es | Spanish |
pt, pt-PT | Portuguese |
de | German |
fr | French |
hi | Hindi |
bn | Bengali |
ru | Russian |
ar | Arabic |
tr | Turkish |
Attribution
The placement id on the tag is the only attribution you need. It travels
with the panel to the chat backend as aon_pid, which adds
X-AON-Placement-Id to every offer query it makes, so impressions, clicks
and conversions land on the placement's application in the Developer
Portal. Conversions arrive on the application's
conversion Webhook.
Verify an integration
curl -s https://your-site/page | grep -c 'data-placement-id="plc_…"'returns1, and the same fordata-aon-slot="end-of-content".curl -s https://api.aon.pro/v1/public/placements/plc_…/setupreturns"status": "live"and a non-emptystyleslist.- On the live page, the floating button appears and opens an
<iframe title="AON">whosesrccontainsaon_pid=plc_…. The End of content spot fills with questions once AON has read the page; a page AON has never seen can take a while on its first visit. - For details, open the browser console with the Verbose level: loader messages start with
[AON].
The Developer Portal's Copy for LLM button on the placement page gives a coding agent this integration as a runbook, including a Playwright script that performs the checks above.