The extension for your app
The extension is the surface that needs no change to the page it assists. That makes it the right one when you cannot embed a widget: an application you do not own, an internal tool nobody wants to redeploy, or a build you are still developing. Everything the widget can do the extension can do, the same configuration serves both, and a page that registers client actions is operated by either one.
This page is for the developer who wants the extension pointed at their own application. For rolling it out to a whole organization, see Enterprise deployment.
Install the build
There is no Chrome Web Store listing while the beta is closed, so the extension is installed by hand:
- Download the ZIP from appilot.space/downloads.
- Unpack it somewhere permanent. Chrome loads it from that folder every time it
starts, so a folder in
~/Downloadsthat you later clear takes the extension with it. - Open
chrome://extensions, turn on Developer mode, choose Load unpacked, and select the unpacked folder.
The build talks to the Appilot cloud. There is no auto-update: when a new
version ships, the backend answers 426 and the panel says so, and you repeat
these steps with the new ZIP.
Sign in
Open the panel and choose to sign in. The extension opens its own page inside the extension origin, and from there it sends you to My Account to sign in with whatever your account already uses: a password, Google, or your organization's SSO. You confirm the handoff there and the session travels back to the extension. The extension asks for no credentials of its own, so a host page never sees one and there is no second set of sign-in methods to keep in step.
One thing still trips people up: your account needs an active membership. An account with no organization, or one whose join request is still pending, signs in and gets nowhere: the turn is refused because there is no tenant to run it in. Create an organization or accept the invitation in My Account first.
How the extension knows which app it is on
The extension does not carry an app id. On every page it asks the backend which tenant the page's hostname belongs to.
- The hostname is matched without the port, so
myapp.lvh.me:3000andmyapp.lvh.me:5173are the same domain. - A hostname belongs to exactly one organization. If another organization
registered it first, your users are refused with
TENANT_DOMAIN_MISMATCHwhen they send a turn, and the panel says the site belongs to a different organization. - On a hostname nobody registered, the turn still runs. It is grounded in the page the user is on and in your organization's own knowledge, with no views, controls, forms or action plans behind it. The panel calls this live page mode.
So the whole of "make the extension use my configuration" is: register the hostname as a domain of your app, and be a member of the organization that owns it.
Verification is not part of this. The extension serves an unverified domain; the TXT record gates the live widget key and the site adapter. See Verify your domain.
On your own machine
Register a hostname that resolves to 127.0.0.1, such as myapp.lvh.me, and
serve your dev build on it. Bare localhost cannot be registered as a domain,
and a test widget key does nothing for the extension, which authenticates as
the user rather than as a key. The full contract is in
Develop on localhost.
Client actions with no widget
A page that registers client actions is operable by the extension with no widget present. The extension reads the page's registry from a main-world bridge at the start of each turn and invokes the handlers through the same channel:
import { registerTool } from 'appilot';
registerTool({
name: 'open_invoice',
description: 'Open an invoice by number.',
inputSchema: { type: 'object', properties: { number: { type: 'string' } } },
annotations: { readOnlyHint: true },
execute: ({ number }) => { router.push(`/invoices/${number}`); },
});
The page must load the appilot runtime for the registry to exist. Everything
else, including the confirmation rule for mutating actions, is identical to the
widget. See Client actions.
Focused Sessions work the same way: startFocusedSession from a page under the
extension reaches the extension's relay provider, so an app-initiated,
mission-scoped conversation runs with no widget embedded. See
Focused Sessions.
The iteration loop
The extension reads the domain and the app configuration per page load, and the content model per turn. So the loop is:
- Change the configuration, in the Backoffice or through your MCP client.
- Reload the page.
- Ask.
There is no cache to clear on the extension side. Changing the extension itself
(rather than your configuration) does need Chrome to reload the unpacked
extension from chrome://extensions.
What a turn can and cannot reach
- A turn belongs to the tab it started in. The tab is fixed when the user sends, and every read and action goes there, so switching tabs while the assistant works does not redirect it. A tab that was closed or navigated to another origin refuses the read.
- Credential fields are never read or written. Passwords, one-time codes, payment-card fields and hidden inputs are excluded from every reader on both surfaces, with no exemption by caller.
- The extension does not mount on Appilot's own hosts (the public site, this documentation, downloads, My Account and the Backoffice), and a user can pause it per site from the context menu.
- Where a page embeds the widget, the extension stands aside so the user never
sees two assistants. The page announces the widget with a
data-appilot-widgetattribute on<html>; the extension announces itself withdata-appilot-extension, which your page can read to detect it.
Testing
Drive the built extension from a real browser rather than a jsdom test: the content script, the isolated world and the main-world bridge are the parts most worth checking, and none of them exists outside Chrome. Load the unpacked build in a browser your coding agent controls (the Chrome DevTools MCP server is the practical route), open your page on the registered hostname, and run a turn.