Appearance
User Context
This page documents the Web SDK user-context API: fetching the current viewer with web.Context.User() and looking up community members with the web.User.* methods. All run as the visiting user, authorized by the community session cookie.
Where the examples run
Every example imports web from @gainsight-hub/sdk and runs in a widget or in a sitewide script registered with "type": "module". See SDK. web forwards to the community's Web SDK and throws on pages without it.
TIP
Prefer these SDK methods over a Connector for public member data — they use the browser session and need no server-side secrets. Private account data, such as email addresses, is not available through the SDK.
Context: Current Viewer
Context.User()
Fetches the current user for the active session. This is the direct replacement for reading inSidedData.user.
Returns: Promise<User>
- Resolves to a
Userobject for an authenticated visitor. - For an unauthenticated (guest) visitor, resolves to the guest projection — a
Userobject withuserId: nullandusername: "guest". It is nevernull; branch onuserIdto tell guest from member. - Throws if called outside a browser environment or if the network request fails.
Takes no parameters. For a signed-in visitor, badges and profileFields are always present (an empty array when there is nothing to show) and rank is present when the user has one. The guest result has badges: [] and no profileFields or rank; see Guest Handling.
Private profile fields are included
Context.User() returns the signed-in member's own profile fields, including the ones set to Private (visibility: 2). Private keeps a field from other members. It does not keep it from scripts running on the community page (analytics, tag managers, chat tools, other widgets), which can read it through the SDK. Do not send profileFields to external services or logs, and filter on visibility === 1 when you need only public values. User.getById() called with the viewer's own ID returns public fields only.
Private account data is never included, even for the signed-in viewer: email address, private-message counts, subscription count, login/registration source, email-campaign opt-in, and custom role IDs.
Checking authenticated vs. guest: branch on userId, not on null.
javascript
import { web } from "@gainsight-hub/sdk";
const me = await web.Context.User()
if (me.userId === null) {
// Guest — show a login prompt or public-only content
} else {
// Authenticated — personalize the UI
console.log(me.username) // 'janedoe'
console.log(me.userId) // 101
console.log(me.rank?.name) // 'Regular'
console.log(me.badges) // [{ id: 100, title: 'First post', url: 'https://assets.example.com/…_thumb.png' }]
console.log(me.profileFields) // [{ id: 3, title: 'Seniority', value: 'senior', ... }]
}User: Lookup and Listing Methods
All methods below are browser-only and operate on the community user directory. They return the public User shape — no private account data (email, private-message counts, login source) and no private-visibility profile fields are exposed by any of them.
User.getById(id)
Fetch a single user by numeric ID.
Returns: Promise<User | null> — resolves to null if the ID does not resolve.
| Parameter | Type | Description |
|---|---|---|
id | number | string | User ID. Numeric strings are accepted and coerced. |
javascript
import { web } from "@gainsight-hub/sdk";
const user = await web.User.getById(101)
if (user) {
console.log(user.username, user.rank?.name)
}User.getUsersById(ids)
Fetch multiple users by an array of IDs in one request (batch lookup). Unresolved IDs are silently omitted — the result array may be shorter than the input.
Up to 100 IDs per request. IDs beyond the hundredth are dropped, not refused.
Returns: Promise<User[]> — array of resolved users. Order is not guaranteed to match the input; match results by userId (e.g. with .find()), not by position. Each item is a User object.
| Parameter | Type | Description |
|---|---|---|
ids | Array<number | string> | User IDs to fetch. Numeric strings are accepted. |
javascript
import { web } from "@gainsight-hub/sdk";
const users = await web.User.getUsersById([101, 202, 303])
// users.length may be less than 3 if any ID did not resolve
users.forEach((u) => console.log(u.userId, u.username))User.list(options?)
List and filter community members with pagination, sorting, and filtering. Returns a paginated result.
Returns: Promise<{ totalItems: number, items: User[] }>
totalItems: total number of users matching the filters (before pagination).items: users on the requested page. Defaults to 25 per page (max 100) whensizeis omitted.- Every row is a fully hydrated
User(rank,badges,profileFieldsincluded), so a full page of 100 is a heavier payload — request only thesizeyou need.
| Option | Type | Default | Description |
|---|---|---|---|
search | string | — | Substring match on username. An authenticated caller may also pass an email address here; a guest who does gets a 403 (guests can still search by username). |
role | number[] | — | Filter by main role ID(s). OR semantics. |
joinDate | DateFilter | — | Filter on registration date. |
lastActivity | DateFilter | — | Filter on last activity date. |
lastVisit | DateFilter | — | Filter on last visit (login) date. |
topics | NumberFilter | — | Filter on topics created. |
replies | NumberFilter | — | Filter on replies created. |
points | NumberFilter | — | Filter on points. |
page | number | 1 | 1-indexed page number. |
size | number | 25 | Page size (1–100). |
sort | UserSortField | 'userId' | Sort field. |
order | 'asc' | 'desc' | 'desc' | Sort direction. |
Role, badge, and profile-field IDs used in the filters and examples below are specific to each community. Find your community's values in its admin settings (the Roles, Badges, and Profile Fields sections) instead of reusing the sample IDs.
DateFilter — ISO-8601 datetime, ISO-8601 duration relative to now (e.g. 'P3D' = last 72 h), or { from?, to? } range:
javascript
import { web } from "@gainsight-hub/sdk";
// Users who joined in the last 7 days
const { items } = await web.User.list({ joinDate: 'P7D' })
// Users who joined in a specific range
const { items } = await web.User.list({
joinDate: { from: '2024-01-01T00:00:00Z', to: '2024-12-31T23:59:59Z' }
})NumberFilter — exact integer or { eq?, gt?, gte?, lt?, lte? } range:
javascript
import { web } from "@gainsight-hub/sdk";
// Users with 10 or more topics
const { items } = await web.User.list({ topics: { gte: 10 } })UserSortField values: 'userId' | 'username' | 'replies' | 'topics' | 'points' | 'lastVisit' | 'joinDate' | 'lastActivity'
There is no sort by total post count. topics and replies sort separately, and any other sort value returns HTTP 400.
Example — list moderators sorted by last visit:
javascript
import { web } from "@gainsight-hub/sdk";
const { totalItems, items } = await web.User.list({
role: [7],
sort: 'lastVisit',
order: 'desc',
size: 20
})
console.log(`${items.length} of ${totalItems} moderators`)
items.forEach((u) => console.log(u.username, u.lastVisit))Example — paginate through all users:
javascript
import { web } from "@gainsight-hub/sdk";
const size = 100
let page = 1
let total = Infinity
while ((page - 1) * size < total) {
const result = await web.User.list({ page, size })
total = result.totalItems
result.items.forEach((u) => console.log(u.userId, u.username))
page++
}User.getRecentlyActive(limit?)
Convenience wrapper around list(). Returns users sorted by lastVisit descending.
Returns: Promise<User[]>
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 10 | Maximum users to return. |
javascript
import { web } from "@gainsight-hub/sdk";
const recentUsers = await web.User.getRecentlyActive(5)
recentUsers.forEach((u) => console.log(u.username, u.lastVisit))User.getByRole(role, options?)
List users whose main role is one of the specified roles. Custom roles are not matched.
Returns: Promise<{ totalItems: number, items: User[] }> — defaults to 25 per page (max 100), same as list().
| Parameter | Type | Description |
|---|---|---|
role | number | number[] | Role ID or array of IDs. OR semantics. |
options | object | Same pagination and sort options as list() (minus role). |
javascript
import { web } from "@gainsight-hub/sdk";
// Users with role id 7, first page
const { totalItems, items } = await web.User.getByRole(7, { size: 50 })
// Users with any of these roles
const { items } = await web.User.getByRole([7, 9])User.search(query)
Search users by a query string. Returns a reduced field set optimized for mention/autocomplete UIs — use User.list() when you need the full user object.
Requires an authenticated session — a guest calling search() receives a 403 (this is a different endpoint from list(), and it is not open to guests at all).
Returns: Promise<UserSearchResult[]> — up to 25 users. This limit is fixed; search() takes no size parameter.
| Parameter | Type | Description |
|---|---|---|
query | string | Non-empty search query |
UserSearchResult object shape:
| Field | Type | Notes |
|---|---|---|
userId | number | Canonical user ID. |
username | string | Display username. "guest" on the guest projection. |
profileUrl | string | null | Absolute URL to the profile page. |
avatar | string | Avatar URL, or "" when none. |
userTitle | string | Display title. |
reputation | number | null | Reputation level, not the raw points. Omitted when no level is available for the user. |
isBanned | boolean | true if the user holds the banned role. |
badges | Badge[] | Hydrated badges, without id. |
rank | Rank | null | Hydrated rank, without id. |
javascript
import { web } from "@gainsight-hub/sdk";
const results = await web.User.search('jane')
results.forEach((u) => console.log(u.userId, u.username, u.avatar))User Object Shape
Every method above except search() returns the unified User model. search() returns the reduced UserSearchResult shape described under User.search(query).
A field with no value is omitted from the User object rather than returned as null. Fields marked (optional) in the tables below can be absent: read them with optional chaining (user.rank?.name) or a presence check, not === null. userId and its mirror id are the exception: they are always present, and null on the guest result. Inside nested objects the rule differs: the Rank object returns its empty fields (such as color or iconUrl) as null, and a ProfileField value can be null.
Not returned today: firstName, lastName, signature, customRoles, solved, likesReceived, likesGiven, followers and following. The generated TypeScript User type still declares some of these as optional, but the backend does not populate them, so reading them always gives undefined.
Core identity fields
| Field | Type | Notes |
|---|---|---|
userId | number | null | Canonical user ID. null only on the guest projection. Use this in new code. |
id | number | null | Deprecated mirror of userId. Kept for backward compatibility; migrate to userId. |
username | string | Display username. "guest" on the guest projection. |
name | string | Deprecated alias of username. |
avatar | string | Avatar URL, or empty string "" when none. |
profileUrl | string (optional) | Absolute URL to the user's profile page (e.g. "https://community.example.com/members/janedoe-101", where 101 is the user ID). The path holds the username in a URL-safe form (lowercase, symbols become hyphens: mod_99 becomes mod-99), so use this value and do not build the link from username. Omitted on the guest result. |
url | string (optional) | Deprecated mirror of profileUrl. Kept for backward compatibility; migrate to profileUrl. |
userTitle | string (optional) | Display title (custom title or rank name; empty string if hidden). Omitted on the guest result. |
companyId | string (optional) | The external ID of the company the user is linked to. Omitted when the user is not linked to a company. It is not the community's "Company" profile field, which is an ordinary text field in profileFields. |
Role and moderation fields
| Field | Type | Notes |
|---|---|---|
isBanned | boolean | true if the user holds the banned role. |
role | number (optional) | Main role ID as an integer. Community-specific. Omitted on the guest result. |
isModerator | boolean | Guest result only (false). A signed-in user's object never has it. |
mainRole | string | Guest result only ("roles.guest"). A signed-in user's object never has it. |
A signed-in user's object has no moderator flag. role is the main role only: custom roles are not returned, and getByRole() and list({ role }) match the main role only. To show something to moderators, compare role with your community's moderator, community manager and administrator role IDs (they are specific to each community, so look them up in the admin Roles settings and never assume the sample ID 7). A role granted only as a custom role is not visible to the SDK.
Rank fields
| Field | Type | Notes |
|---|---|---|
rankId | number (optional) | Rank ID. Omitted when the user has no rank. |
rank | Rank (optional) | Hydrated rank with display styling. Omitted when the user has no rank; see below. |
A user has no rank until the community's rank conditions assign one (a new member starts without one), and loses it when no rank condition matches. rank is also omitted when the community has turned the rank feature off or the rank no longer exists; in those two cases rankId can still be present, so test rank, not rankId, before rendering a rank.
Rank object shape:
| Field | Type | Notes |
|---|---|---|
id | number | Rank ID |
name | string | Rank display name (e.g. "Regular") |
color | string | null | Hex color for the rank name (e.g. "#3366ff") |
isBold | boolean | Whether the rank name should be bold |
isItalic | boolean | Whether the rank name should be italic |
isUnderline | boolean | Whether the rank name should be underlined |
icon | string | null | Icon identifier |
iconUrl | string | null | URL to the rank icon image |
avatarIcon | string | null | Avatar icon identifier |
avatarIconUrl | string | null | URL to the avatar icon image |
Badge fields
| Field | Type | Notes |
|---|---|---|
badges | Badge[] | Hydrated badges. Always present. Unresolvable badge IDs are dropped — the array only contains objects. |
Badge object shape:
| Field | Type | Notes |
|---|---|---|
id | number | Badge ID |
title | string | Badge display name (e.g. "First post") |
url | string | Absolute URL of the badge's thumbnail image (e.g. "https://assets.example.com/attachment/3f2a_thumb.png"). Empty string "" when the badge has no image. |
Group and profile fields
| Field | Type | Notes |
|---|---|---|
groups | number[] | Group IDs the user belongs to. |
profileFields | ProfileField[] | Custom profile fields stored for this user that the viewer is allowed to see, in the community's display order; see below. |
profileFields is not the community's full field list: one user can have 1 entry and another 17, so look a field up by id, never by position. To find a field's id, log your own web.Context.User() and read profileFields. A field titled "Email" is an ordinary profile field, not the account email address, which is never returned.
ProfileField object shape:
| Field | Type | Notes |
|---|---|---|
id | number | Profile field ID |
title | string | Profile field label (e.g. "Seniority") |
type | string | Field type: 'text', 'check', 'select', 'radio', 'birthday' or 'date'. |
value | string | null | The value as stored. An empty text field is ""; an unset select, check or date field is null. Treat both as "no value". For select and radio fields it is the stored option value (for example "india"), not necessarily the label members see. |
visibility | number | Visibility level of the field: 1 public, 2 private. Only Context.User() returns private fields (the viewer's own); every other method returns public ones. Hidden fields are never returned. |
Activity and engagement fields
| Field | Type | Notes |
|---|---|---|
topics | number | Topics created. |
replies | number | Replies created. |
points | number | Points. |
reputation | number (optional) | Reputation level, not the raw points (points is a separate field). Omitted when no level is available for the user. |
userLevel | number (optional) | Deprecated alias of reputation, returned by getUsersById() only. |
Date fields
All three are UTC timestamps as ISO-8601 strings, written with a +00:00 offset (for example "2024-03-18T09:30:00+00:00"). new Date(value) parses them.
| Field | Type | Notes |
|---|---|---|
joinDate | string (optional) | Registration timestamp as ISO-8601 datetime (e.g. "2024-03-18T09:30:00+00:00"). Omitted on the guest result. Not a Unix timestamp — the legacy inSidedData.user.joindate was a Unix int; this is a string. |
lastActivity | string (optional) | Last activity timestamp (ISO-8601). Equals joinDate until the user has activity. Omitted on the guest result. |
lastVisit | string (optional) | Last visit/login timestamp (ISO-8601). Omitted when the user has never visited, and on the guest result. |
No private account data
The User object never carries private account data — no email address, private-message counts, subscription count, login/registration source, email-campaign opt-in, or custom role IDs. These are stripped on the server for every method, including Context.User() for the authenticated viewer. Private profile fields are the one exception: only Context.User() returns them, and only the viewer's own. Obtain private account data server-side through a Connector if a widget genuinely needs it.
Guest Handling
When an unauthenticated visitor calls web.Context.User(), it resolves to the guest projection — a User object with userId: null, username: "guest", and zeroed counts. It is never null, so always branch on me.userId === null before treating the viewer as a member.
The guest result contains only these keys: userId and id (both null), username and name ("guest"), avatar (""), badges and groups (both []), isBanned (false), isModerator (false), mainRole ("roles.guest"), and topics, replies and points (all 0). It has no profileFields, rank, role, profileUrl or date fields, so guard reads such as me.profileFields and me.joinDate behind the userId === null check.
For web.User.list() and related methods, guests get the public field set. Email-based search requires an authenticated session (guests receive a 403).
Private communities
On a private community, unauthenticated requests are redirected to the login page instead of returning data, so the SDK call rejects rather than resolving. Wrap calls in try/catch and treat a rejection as "not signed in."
Field Availability by Method
User.search() returns a reduced set optimized for autocomplete. Every other method — Context.User(), getById(), getUsersById(), list(), getByRole(), getRecentlyActive() — returns the full public User shape. No method exposes private account data, and only Context.User() returns private-visibility profile fields (the viewer's own).
| Field group | Context.User() | Other User.* lookups | search() |
|---|---|---|---|
Base scalars (userId, username, avatar, …) | ✅ | ✅ | Partial |
rank | ✅ | ✅ | ✅ (without id) |
badges | ✅ | ✅ | ✅ (without id) |
profileFields (public visibility) | ✅ | ✅ | ❌ |
profileFields (private visibility, the viewer's own) | ✅ | ❌ | ❌ |
| Private account data (email, PM counts, subscriptions, login/register source, email-campaign opt-in, custom role IDs) | ❌ | ❌ | ❌ |
Legacy Field Migration
If you are migrating code that reads inSidedData.user (or the getUsersById response, which carried url and userLevel), use this table to find the canonical field name:
| Legacy field | Canonical field |
|---|---|
userid (int) | userId |
name | username |
url | profileUrl |
userLevel | reputation |
rankName | rank.name |
rankIcon | rank.avatarIconUrl (rank.iconUrl is the separate username icon) |
topicsCount | topics |
repliesCount | replies |
joindate (Unix int) | joinDate (ISO-8601 string — different type) |
Some legacy inSidedData.user fields have no canonical equivalent:
- Private data —
email,pmUnreadCount,pmTotalCount,subscriptions,loginSource,registerSource. No SDK method exposes them; read them server-side through a Connector if required. - Engagement counts —
solvedCount,likes,likes_given. No SDK method returns them. - Moderation —
isModerator,mainRoleand the comma-separatedrole. The SDK returnsisModeratorandmainRoleonly for guests, androleis the main role ID alone, so a script that gated onisModeratormust comparerolewith your moderator role IDs instead. Custom roles are not returned.
Profile fields are broader than before
inSidedData.user.profileFields held public fields only. Context.User().profileFields also holds the viewer's own private fields, so a script that was safe to log or forward with the legacy list is not safe to do so with this one.
joinDate type change
inSidedData.user.joindate was a Unix timestamp (integer). The canonical joinDate is an ISO-8601 datetime string. If you compare or format dates, update your parsing logic.
Error Handling
All methods throw on failure. Wrap calls in try/catch:
javascript
import { web } from "@gainsight-hub/sdk";
try {
const me = await web.Context.User()
if (me.userId === null) {
// Guest — handle gracefully
} else {
// Use me.username, me.rank, etc.
}
} catch (err) {
console.error('Failed to fetch user context:', err.message)
}Common error conditions:
| Scenario | Cause |
|---|---|
"Context.User can only be called in a browser environment" | Called outside a browser (e.g. during SSR or in Node). |
| HTTP 400 | Invalid filter operator, an unsupported sort field (e.g. sort=email), or an unsupported filter passed to list(). |
| HTTP 403 | A guest called User.search() (authenticated-only), or passed an email-address term to list({ search }). Guest username search via list() is allowed. |
| HTTP 5xx | A server error occurred. Retry after a brief delay. |
| Rejected call (private community) | Unauthenticated requests are redirected to a login page instead of returning data; catch it and treat as "not signed in." |
The SDK surfaces these as a rejected promise (a thrown Error), not as a structured status code you can switch on — the "Scenario" column describes the underlying cause, not a machine-readable field. Branch on whether the call rejected, and inspect err.message for detail; there is no reliable err.status to implement per-code handling.
Next Steps
- SDK Examples — runnable, copy-paste code for
Context.User()and theUser.*methods - SDK Reference — full reference for all SDK namespaces
- SDK — how the import resolves and project setup
- Passing User Context — use server-side template variables when building Connectors that need the viewer's identity

