Implementing Embedded Knowledge Base Search Widgets in the Agent Desktop Sidebar
What This Guide Covers
You will build a React-based UI Extension that mounts to the Agent Desktop sidebar, queries the Genesys Cloud Knowledge API, and renders contextual search results. The final implementation provides agents with a dockable, auto-suggesting knowledge panel that resolves queries without context switching or browser tab navigation.
Prerequisites, Roles & Licensing
- Licensing: Genesys Cloud CX 2 or CX 3 tier (Knowledge API access requires CX 2 minimum)
- Permissions:
Knowledge > Article > Read,Knowledge > Category > Read,Knowledge > Search > Read,UI Extensions > Extension > Deploy,UCP > Interaction > Read - OAuth Scopes:
knowledge:article:read,knowledge:search:read,ucp:interaction:read - External Dependencies: Node.js 18+,
@genesyscloud/extension-sdk,@genesyscloud/ui-components, existing Knowledge base with published articles and configured search indexing
The Implementation Deep-Dive
1. Extension Manifest Configuration & Permission Binding
The manifest file dictates how the Genesys Cloud runtime loads, scopes, and isolates your extension. You must define the extension type as ui-extension and declare explicit permission boundaries. The platform enforces least-privilege execution at the container level, so missing scopes will cause silent 403 Forbidden responses during API calls.
Create manifest.json with the following structure:
{
"extensionType": "ui-extension",
"id": "kb-sidebar-search-widget",
"version": "1.0.0",
"displayName": "Knowledge Base Search Widget",
"description": "Sidebar-mounted knowledge search for Agent Desktop",
"permissions": [
"knowledge:article:read",
"knowledge:search:read",
"ucp:interaction:read"
],
"oauthScopes": [
"knowledge:article:read",
"knowledge:search:read",
"ucp:interaction:read"
],
"entryPoint": "src/index.tsx",
"docking": {
"target": "agent-desktop-sidebar",
"position": "right",
"defaultWidth": 320,
"minWidth": 280,
"maxWidth": 480
}
}
The Trap: Developers frequently bind knowledge:article:read but omit knowledge:search:read. The Knowledge API separates content retrieval from full-text indexing access. Without knowledge:search:read, the GET /api/v2/knowledge/articles/search endpoint returns a 403 regardless of role assignments. Additionally, over-scoping with knowledge:article:write triggers security compliance alerts and fails deployment in environments enforcing strict RBAC policies.
Architectural Reasoning: We declare ucp:interaction:read alongside knowledge scopes because the widget requires context awareness. Passing the active interaction ID allows the extension to filter knowledge results by queue, skill, or customer segment. The docking configuration explicitly constrains width parameters to prevent layout overflow when the sidebar collapses during screen sharing or high-DPI rendering. The Genesys Cloud UI framework uses CSS grid constraints that break when extensions exceed declared dimensions. We also separate permissions and oauthScopes because the manifest validates role-based access at deployment time while OAuth scopes govern runtime token claims. This dual-layer validation prevents privilege escalation when extensions are shared across multiple orgs.
2. React Component Implementation & Knowledge API Integration
The core search logic resides in a React component that intercepts user input, debounces keystrokes, and executes REST calls against the Knowledge API. You will use the @genesyscloud/extension-sdk to retrieve the active OAuth token and inject it into request headers. Direct REST calls provide deterministic control over payload shaping, retry logic, and cache invalidation.
Initialize the component structure:
import React, { useState, useCallback, useEffect } from 'react';
import { useAuthContext, useSdkContext } from '@genesyscloud/extension-sdk';
import { debounce } from 'lodash-es';
interface ArticleResult {
id: string;
title: string;
summary: string;
categories: string[];
knowledgeArticleStatus: string;
}
const KnowledgeSearchWidget: React.FC = () => {
const { getToken } = useAuthContext();
const sdk = useSdkContext();
const [query, setQuery] = useState('');
const [results, setResults] = useState<ArticleResult[]>([]);
const [isSearching, setIsSearching] = useState(false);
const executeSearch = useCallback(async (searchTerm: string) => {
if (!searchTerm.trim()) {
setResults([]);
return;
}
setIsSearching(true);
const token = await getToken();
const orgId = sdk.core.auth.getOrganizationId();
const endpoint = `/api/v2/knowledge/articles/search?orgId=${orgId}&q=${encodeURIComponent(searchTerm)}&size=8&status=published`;
try {
const response = await fetch(endpoint, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Knowledge API returned ${response.status}`);
}
const data = await response.json();
setResults(data.items || []);
} catch (error) {
console.error('Knowledge search failed:', error);
setResults([]);
} finally {
setIsSearching(false);
}
}, [getToken, sdk]);
const debouncedSearch = useCallback(
debounce(executeSearch, 350, { leading: false, trailing: true }),
[executeSearch]
);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return (
<div className="kb-search-container">
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder="Search knowledge base..."
aria-label="Knowledge search input"
/>
{isSearching && <div className="search-spinner" />}
<ul className="results-list">
{results.map(article => (
<li key={article.id} className="article-item">
<h4>{article.title}</h4>
<p>{article.summary}</p>
</li>
))}
</ul>
</div>
);
};
export default KnowledgeSearchWidget;
The Trap: Implementing synchronous fetch calls without debouncing triggers rate limiting immediately. The Knowledge API enforces a sliding window rate limit of 60 requests per minute per OAuth token. Typing a 10-character query without debouncing generates 10 concurrent requests, resulting in 429 Too Many Requests responses and degraded agent experience. Additionally, failing to encode the q parameter with encodeURIComponent breaks queries containing special characters, spaces, or non-ASCII text. The Knowledge API parser rejects malformed UTF-8 sequences and returns a 400 Bad Request with an empty payload.
Architectural Reasoning: We use lodash-es debounce with a 350ms delay because empirical load testing shows this window balances responsiveness with API conservation. The leading: false, trailing: true configuration ensures the final keystroke triggers the request, not the first. We inject the organization ID into the query string because multi-org deployments require explicit tenant routing. The Knowledge API ignores unscoped orgId parameters and defaults to the authenticated user home tenant, which causes data leakage or missing results in federated environments. A typical successful response payload contains nested category objects and versioning metadata. You must flatten the categories array into a string for display, otherwise the virtualized list renders raw JSON objects that break accessibility screen readers.
3. Sidebar Docking, Context Passing & Performance Hardening
The extension must mount to the Agent Desktop sidebar using the docking API and consume interaction context to filter results dynamically. You will register the component with the @genesyscloud/ui-components layout manager and bind it to the active UCP interaction lifecycle. The sidebar operates as a virtualized container, so memory management and DOM recycling are mandatory.
Implement the docking registration and context binding:
import { registerExtension, useDockingContext } from '@genesyscloud/extension-sdk';
import { useInteractionContext } from '@genesyscloud/ucp-sdk';
import KnowledgeSearchWidget from './KnowledgeSearchWidget';
registerExtension('kb-sidebar-search-widget', {
render: () => {
const { dock } = useDockingContext();
const { activeInteraction } = useInteractionContext();
useEffect(() => {
if (activeInteraction) {
dock.registerPanel({
id: 'kb-search-panel',
title: 'Knowledge Search',
component: <KnowledgeSearchWidget interactionId={activeInteraction.id} />,
resizable: true,
collapsible: true
});
}
}, [activeInteraction, dock]);
return null;
}
});
The Trap: Registering the panel inside a useEffect without cleanup causes memory leaks when agents switch interactions or close the UCP window. The Genesys Cloud runtime does not garbage collect docked panels automatically. You must call dock.unregisterPanel('kb-search-panel') on interaction teardown. Additionally, passing the entire activeInteraction object as a prop triggers unnecessary re-renders because the interaction payload updates on every status change, timer tick, or DTMF event. This causes the React