Domains
Help Center
Fetch help center channels, KB collections, and articles.
- Use it for: Public website · Server integration
- Reads: public token — works in the browser
- Writes: Bearer (
apiKey) — server-side only - CLI:
uidu channel - Auth: what each token can do and where it may run — Authentication
The help center is a workspace's self-service support site: channels of knowledge-base
articles, grouped by topic. getChannel reads a whole channel — its collections and their
articles — in one request, which is usually all a help-center front end needs.
Mental model
Workspace
└── Channel { id, name, isDefault } — the help center itself
├── kbCollections topics: { id, name, description, iconData }
│ └── kbArticles { id, name, body, updatedAt, workspaceTagList }
└── channelKbCollections { position, kbCollection { id } } — the order inside this channel- Which channel. A workspace can have several.
listChannelsmarks the main one withisDefault: code that has no channel id configured should prefer it over the first row. - One request, capped.
getChannelfetches the last 100 collections and the last 100 articles of each. Past that, read collections and articles directly with the Knowledge base functions, which paginate. - Order. Articles inside a collection come back ordered by their
position. A collection's position in this channel lives inchannelKbCollections(bykbCollection.id), not on the collection — sort with it when the order matters. - Drafts are left out server-side: every article you get here is published.
- Field shapes. A collection's
nameanddescriptionare JSON — usually a string, possibly a localization map like{ en, it };iconDatais icon metadata (JSON). An article'snameis a string, itsbodyis rich-text JSON (render it with<RichText>), andworkspaceTagListis its tags,{ id, name, color }. - Search. Knowledge-base articles aren't in the workspace search index, so
searchwon't find them: filter the channel's articles in memory. - Writes.
createChannel,updateChannelanddeleteChannelneed the Bearer client and return their payload witherrors(the new channel is atchannel.node). Collections and articles are written through the Knowledge base functions.
Use cases
Help center home
Goal: a /help page that shows the default channel's topics in the channel's own order, each with
its articles.
// src/lib/help.ts
import { cache } from 'react';
import { getChannel, listChannels } from '@uidu/client';
import { uidu } from './uidu';
export const getHelpChannel = cache(async () => {
const channels = await listChannels(uidu, { first: 25 });
const main = channels.find((c) => c.isDefault) ?? channels[0];
return main ? getChannel(uidu, { id: main.id }) : null;
});
// A collection's name/description may be a plain string or a { en, it, … } map.
export function label(value: unknown, locale = 'en'): string {
if (typeof value === 'string') return value;
if (value && typeof value === 'object') {
const map = value as Record<string, unknown>;
return String(map[locale] ?? Object.values(map)[0] ?? '');
}
return '';
}
// src/app/help/page.tsx
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { getHelpChannel, label } from '@/lib/help';
export default async function HelpHomePage() {
const channel = await getHelpChannel();
if (!channel) notFound();
const position = new Map(
(channel.channelKbCollections?.edges ?? []).flatMap((e) =>
e?.node ? [[e.node.kbCollection.id, e.node.position] as const] : [],
),
);
const collections = (channel.kbCollections?.edges ?? [])
.flatMap((e) => (e?.node ? [e.node] : []))
.sort((a, b) => (position.get(a.id) ?? 0) - (position.get(b.id) ?? 0));
return (
<div>
<h1>{channel.name}</h1>
{collections.map((c) => (
<section key={c.id}>
<h2>{label(c.name)}</h2>
<p>{label(c.description)}</p>
<ul>
{(c.kbArticles?.edges ?? []).map(
(a) =>
a?.node && (
<li key={a.node.id}>
<Link href={`/help/${encodeURIComponent(a.node.id)}`}>{a.node.name}</Link>
</li>
),
)}
</ul>
</section>
))}
</div>
);
}- Set the channel id in an env var instead of
listChannelsif you want to pin one: ids are GlobalIDs, so they belong to one workspace and don't travel between workspaces. cache()lets the home and the article page share onegetChannelper render.
Article page
Goal: /help/[articleId] renders one article with its body, under its topic.
// src/app/help/[articleId]/page.tsx
import { notFound } from 'next/navigation';
import { getKbArticle } from '@uidu/client';
import { RichText } from '@uidu/react';
import { label } from '@/lib/help';
import { uidu } from '@/lib/uidu';
export default async function ArticlePage({
params,
}: {
params: Promise<{ articleId: string }>;
}) {
const { articleId } = await params;
const article = await getKbArticle(uidu, { id: decodeURIComponent(articleId) });
if (!article) notFound();
return (
<article>
{article.kbCollection && <p>{label(article.kbCollection.name)}</p>}
<h1>{article.name}</h1>
<RichText doc={article.body} />
<small>Updated {new Date(article.updatedAt as string).toLocaleDateString()}</small>
</article>
);
}getKbArticlefetches the one article instead of the whole channel. If you already hold the channel (fromgetHelpChannel()), the article'sbodyis in it too — look it up there and save a request.getKbArticledoesn't check which channel the article belongs to. If you run several channels and must keep them apart, find the article inside the channel instead.
Filter articles on the page
Goal: a quick filter over the help center, by text or by tag, without a server round trip.
// src/app/help/article-filter.tsx
'use client';
import { useState } from 'react';
import Link from 'next/link';
type Item = { id: string; name: string | null; tags: string[] };
export function ArticleFilter({ items }: { items: Item[] }) {
const [q, setQ] = useState('');
const needle = q.trim().toLowerCase();
const hits = needle
? items.filter(
(a) =>
a.name?.toLowerCase().includes(needle) ||
a.tags.some((t) => t.toLowerCase().includes(needle)),
)
: items;
return (
<>
<input
type="search"
value={q}
onChange={(e) => setQ(e.target.value)}
aria-label="Filter articles"
/>
<ul>
{hits.map((a) => (
<li key={a.id}>
<Link href={`/help/${encodeURIComponent(a.id)}`}>{a.name}</Link>
</li>
))}
</ul>
</>
);
}
// In the Server Component, flatten the channel into plain props:
const items = collections.flatMap((c) =>
(c.kbArticles?.edges ?? []).flatMap((e) => {
const a = e?.node;
if (!a) return [];
const tags = (a.workspaceTagList ?? []).flatMap((t) => (t.name ? [t.name] : []));
return [{ id: a.id, name: a.name ?? null, tags }];
}),
);
// <ArticleFilter items={items} />- Pass only what the filter needs: the article
bodyis large and doesn't need to reach the browser.
API at a glance
Every @uidu/client function in Help center, generated from the source — the full reference has the rest of the SDK.
| Function | Kind | Auth | CLI | Description |
|---|---|---|---|---|
createChannel | write | Bearer | channel create | Create a help-center channel. Returns the payload (with errors) or null. The new channel is at channel.node. |
deleteChannel | write | Bearer | channel delete | Delete a help-center channel by { input: { id } }. Returns the payload (with errors) or null. |
getChannel | read | public token | channel get | One help center channel with its collections and their articles in a single request (the last 100 of each), or null. |
listChannels | read | public token | channel list | The workspace's help center channels. isDefault marks the one the workspace treats as its main channel — a caller that has no channel id configured should prefer it over the first row. |
updateChannel | write | Bearer | channel update | Update a help-center channel: { input: { id, attributes } }. Returns the payload (with errors) or null. |
Types: Channel
CLI:
| Entity | list | get | create | update | delete |
|---|---|---|---|---|---|
channel | ✓ | ✓ | ✓ | ✓ | ✓ |