SDK reference
The SDK is exposed as window.uj. All methods can be called at any time, even before the SDK has loaded. Calls are queued and run once ready.
uj.version
The SDK version as a string, for example 1.0.0.
uj.init(projectId, options?)
Initializes the SDK. Returns a promise that resolves with the SDK state once loading settles.
const state = await uj.init('YOUR_PROJECT_ID', {
widget: {
launcher: true, // boolean or { selector: string }
whispers: true, // default whisper behavior
notifications: { updates: 'whisper' }, // 'whisper', 'full', or 'none'
position: 'right', // 'left' or 'right'
offset: { bottom: 20 }, // corner distance in pixels: { bottom, side }
theme: 'auto' // 'auto', 'light', or 'dark'
},
locale: 'es-MX', // optional language override
pageContext: 'origin', // 'origin', 'path', or 'none'
onReady: () => {},
onError: (error) => {}
});Option | Default | Description |
|---|---|---|
|
|
|
|
|
|
|
| Ephemeral notification toasts at the widget corner. Independent of the launcher: they keep appearing when the button is hidden unless set to |
|
|
|
|
| Which side of the screen the widget opens on. |
|
| Pixel distance of the launcher and whispers from the viewport edges. Useful when your own button occupies the corner. |
|
| Follow the system, or force light or dark. |
| workspace setting | Language. Falls back to the workspace language, then the browser. Supported: Arabic, Chinese, Dutch, English, French, German, Italian, Persian, Polish, Portuguese (Brazil and Portugal), Russian, Spanish, Ukrainian. Regional tags such as |
|
| How much of the current page URL is shared with your team in conversations. |
| Called when the SDK is ready. | |
| Called on any SDK error. |
The returned promise does not reject on network problems. Check state.status === 'ready' if you need to confirm loading succeeded. It rejects only for programmer errors: an empty project ID or malformed options.
An empty notifications object or updates: undefined uses the default. Malformed notification objects, unknown keys, and values outside 'whisper', 'full', and 'none' reject initialization with INVALID_OPTIONS and recoverable: false before network or DOM work.
Full mode works without a launcher, other whispers, or host subscriptions. It can show an old latest entry on the first visit and attempts one automatic opening per initialization. Manual open, toggle, or close spends that visit's allowance too. Failed or cancelled attempts are not retried during that visit. Identity changes and app navigation do not reset it; reload or reinitialization does. widget: false disables automatic presentation.
Opening requires a visible, focused page and a closed widget. An actual visible article view, through any entry point, records the article as seen. Prefetches, clicks alone, hidden content, and failed loads do not. Reading older content cannot consume a newer release. Seen history is browser-local and scoped by project and host origin, with no cross-device or cross-tab delivery guarantee. Checks use initialization, focus, and the existing roughly five-minute closed-widget poll. See Update announcements for examples and storage limits.
uj.ready()
Returns the same promise as init. Useful when the code that needs the SDK is far away from the code that initialized it.
const state = await uj.ready();uj.identify(input)
Identifies the current user. Returns a promise with the result. See Identify users and Signed identity.
const result = await uj.identify({
token: 'OPTIONAL_JWT_FROM_YOUR_SERVER',
user: {
id: 'user-123', // required if no token is given
email: 'ada@example.com',
firstName: 'Ada',
lastName: 'Lovelace',
avatar: 'https://example.com/avatar.jpg',
traits: { plan: 'pro' }
},
companies: [{ id: 'acme', name: 'ACME Inc.', domain: 'acme.com', traits: {} }]
});
result.viewer; // the identified user, or null (shape below)
result.identityTrust; // 'signed', 'unsigned', or null
result.warnings; // [{ code, message, field? }]viewer has this shape:
{
id: 'mem_...', // UserJot's member ID, not the user.id you sent
firstName: 'Ada', // string or null
lastName: 'Lovelace', // string or null
avatar: { url: 'https://...', blurhash: '...' } // or null; blurhash optional
}Each entry in warnings names something that was sent but not applied. The call still succeeds.
Code |
| Meaning |
|---|---|---|
| The trait key | The key is reserved by UserJot and was dropped. |
| The company | The workspace requires signed company membership and this company was not in the token. |
|
| The user is a workspace admin or owner. Their traits and companies apply; their name, avatar, and email are managed in the dashboard. |
At least one of token or user.id is required. Calling identify again with the same input is a no-op while the resulting session is still live; after it expires, the same input is sent again. Calling it with new input switches the session. If several calls overlap, the last one wins.
This is the only method that rejects on runtime failures, with IDENTIFY_FAILED.
uj.logout()
Clears the current identity and returns the session to anonymous.
await uj.logout();uj.open(target?)
Opens the widget. With no argument, opens where the user left off. Resolves when the widget is visible.
await uj.open();
await uj.open({ to: 'home' });
await uj.open({ to: 'feedback' });
await uj.open({ to: 'feedback', compose: true });
await uj.open({ to: 'feedback', submissionId: 'POST_ID' });
await uj.open({ to: 'roadmap' });
await uj.open({ to: 'updates' });
await uj.open({ to: 'updates', updateId: 'UPDATE_ID' });
await uj.open({ to: 'messages' });
await uj.open({ to: 'messages', compose: { text: 'Prefilled message' } });
await uj.open({ to: 'notifications' });
await uj.open({ to: 'help' });
await uj.open({ to: 'help', article: 'ARTICLE_SLUG' });
await uj.open({ to: 'help', collection: 'COLLECTION_SLUG' });
await uj.open({ to: 'help', query: 'Search text' });The help target takes at most one of article, collection, or query. article and collection take the slug from the page's help center URL, and an old slug still opens the page it moved to.
Rejects with INVALID_NAVIGATION for unknown targets or invalid combinations, like passing both submissionId and compose, or both article and query.
uj.close()
Hides the widget. Synchronous. Safe to call at any time.
uj.toggle()
Opens the widget if closed, closes it if open.
uj.createEmbed(options?)
Renders a UserJot page inside a container on your page. Returns a controller. See Embedding pages inline.
const embed = uj.createEmbed({
container: '[data-userjot-embed]', // selector or element
path: '/', // initial page
basePath: '/feedback', // sync with your browser URL
theme: 'auto',
onReady: () => {},
onError: (error) => {}
});
embed.navigate('/roadmap');
embed.destroy();Multiple embeds can exist at the same time. All of them follow the current identity.
uj.setLocale(locale)
Changes the widget language at runtime.
await uj.setLocale('pt-BR');uj.setTheme(theme)
Changes the widget theme at runtime. Accepts 'auto', 'light', or 'dark'. Synchronous, unlike setLocale.
uj.setTheme('dark');Invalid values report INVALID_OPTIONS through the error event without changing the theme. Note that init always applies its own theme, which defaults to 'auto' when you do not pass one, so a setTheme call made before init does not survive it. Pass the theme in the init options instead.
uj.getState()
Returns a read-only snapshot of the SDK state.
{
status: 'ready', // 'idle' | 'initializing' | 'ready' | 'error' | 'destroyed'
projectId: 'proj_...',
locale: 'en-US',
unreadCount: 3, // number, or null before the first fetch;
// includes conversation replies, changelog posts,
// and feedback notifications
presence: 'online', // 'online' | 'neutral' — team availability (the green dot)
lastError: null, // the last error, or null
widget: {
enabled: true,
open: false,
position: 'right', // 'left' | 'right'
theme: 'auto' // 'auto' | 'light' | 'dark'
},
session: {
status: 'identified', // 'anonymous' | 'identifying' | 'identified'
identityTrust: 'signed',
viewer: { id: 'mem_...', firstName: 'Ada', lastName: 'Lovelace', avatar: null }
}
}uj.on(event, listener)
Subscribes to an event. Returns an unsubscribe function.
const off = uj.on('unread', ({ count }) => updateBadge(count));
off();Event | Payload | When it fires |
|---|---|---|
| state | Loading settled. Fires immediately if already settled. |
| state | Any state change. |
| state | Widget became visible. |
| state | Widget was hidden. |
|
| Identity changed, including logout. |
|
| Unread count changed. |
|
| Team availability flipped between |
| error | Any SDK error. |
| state | The SDK was destroyed. |
Listeners survive destroy(), so you can subscribe once at page load even if you destroy and re-init the SDK.
presence is the same signal as the green dot inside the widget. 'neutral' means the SDK cannot currently prove anyone from the team is online — it never claims the team is offline, so treat it as "hide the dot", not "show offline". Subscribing to unread or presence keeps both values fresh in the background, even with the default launcher disabled.
uj.destroy(options?)
Removes the widget, all embeds, and the session from the page.
uj.destroy();
uj.destroy({ clearConversationData: true }); // also clear local conversation dataclearConversationData additionally clears the browser's local conversation state: anonymous conversation access grants, drafts, and remembered contact details. Use it on shared devices or kiosk-style setups where the next visitor should not see the previous visitor's guest conversations.
After destroy, you can call init again to start fresh.
Errors
Errors have the shape { code, message, recoverable, cause? }. They reach you through rejected promises for programmer errors, and through the error event and onError callback for everything else.
Code | Meaning |
|---|---|
|
|
|
|
|
|
|
|
| The server rejected the identify call, for example an invalid token. |
| The SDK could not load. Check the project ID and network. |
| A call ran while the SDK was in a failed state. |
|
|
| Messages are disabled for this workspace. The widget opens home instead. |
| The help center is not live for this workspace. The widget opens home instead. |
| The |
| The widget UI failed to load. |
| The embed container does not exist. |
| A call ran after |
| Anything else. |