SDK reference

Shayan TaslimUpdated

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.

JavaScript
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

widget

true

true shows the widget with defaults. false disables it. An object configures it.

widget.launcher

true

false hides the default button. { selector } binds the widget to your own element.

widget.whispers

true

Ephemeral notification toasts at the widget corner. Independent of the launcher: they keep appearing when the button is hidden unless set to false. An explicit update notification mode overrides this for changelog announcements only. See Controlling the widget.

widget.notifications.updates

'whisper', or 'none' when whispers: false

'whisper' announces updates in a toast; 'full' opens the latest unseen entry in the existing reader; 'none' suppresses update announcements while preserving unread. Explicit values override whispers for updates only.

widget.position

'right'

Which side of the screen the widget opens on.

widget.offset

{ bottom: 20, side: 20 }

Pixel distance of the launcher and whispers from the viewport edges. Useful when your own button occupies the corner.

widget.theme

'auto'

Follow the system, or force light or dark.

locale

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 es-MX resolve to their language.

pageContext

'origin'

How much of the current page URL is shared with your team in conversations. 'origin' sends the domain, 'path' includes the path, 'none' sends nothing.

onReady

Called when the SDK is ready.

onError

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.

JavaScript
const state = await uj.ready();

uj.identify(input)

Identifies the current user. Returns a promise with the result. See Identify users and Signed identity.

JavaScript
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:

JavaScript
{
  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

field

Meaning

RESERVED_TRAIT_KEY_IGNORED

The trait key

The key is reserved by UserJot and was dropped.

UNSIGNED_COMPANY_IGNORED

The company id

The workspace requires signed company membership and this company was not in the token.

PRIVILEGED_PROFILE_IGNORED

firstName, lastName, avatar, or email

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.

JavaScript
await uj.logout();

uj.open(target?)

Opens the widget. With no argument, opens where the user left off. Resolves when the widget is visible.

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

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

JavaScript
await uj.setLocale('pt-BR');

uj.setTheme(theme)

Changes the widget theme at runtime. Accepts 'auto', 'light', or 'dark'. Synchronous, unlike setLocale.

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

JavaScript
{
  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.

JavaScript
const off = uj.on('unread', ({ count }) => updateBadge(count));
off();

Event

Payload

When it fires

ready

state

Loading settled. Fires immediately if already settled.

statechange

state

Any state change.

open

state

Widget became visible.

close

state

Widget was hidden.

identify

{ viewer, identityTrust }

Identity changed, including logout.

unread

{ count }

Unread count changed.

presence

{ availability }

Team availability flipped between 'online' and 'neutral'.

error

error

Any SDK error.

destroy

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.

JavaScript
uj.destroy();
uj.destroy({ clearConversationData: true }); // also clear local conversation data

clearConversationData 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

INVALID_PROJECT_ID

init was called with an empty project ID.

INVALID_OPTIONS

init or setLocale received malformed options, or setTheme received a value other than 'auto', 'light', or 'dark'.

INVALID_NAVIGATION

open received an unknown or invalid target.

INVALID_IDENTIFY

identify input is missing both token and user.id.

IDENTIFY_FAILED

The server rejected the identify call, for example an invalid token.

BOOTSTRAP_FAILED

The SDK could not load. Check the project ID and network.

NOT_READY

A call ran while the SDK was in a failed state.

WIDGET_DISABLED

open was called but the widget is disabled.

CONVERSATIONS_DISABLED

Messages are disabled for this workspace. The widget opens home instead.

HELP_DISABLED

The help center is not live for this workspace. The widget opens home instead.

LAUNCHER_NOT_FOUND

The launcher.selector element does not exist.

SIDEBAR_LOAD_FAILED

The widget UI failed to load.

EMBED_CONTAINER_NOT_FOUND

The embed container does not exist.

DESTROYED

A call ran after destroy().

UNKNOWN

Anything else.

Did this answer your question?