https://mcp.creatorland.com/mcpBuild a creator card
A search result is ready to render. Each search_creators row carries a handle, a follower count, a niche, and, on paid plans, an avatar. This recipe turns one row into a creator card, and handles the case every consumer must handle: avatar is null.
Avatars are part of the avatar entitlement and are rolling out on paid plans, so treat the image as a capability that may be present, not one that is on every row today. When it is absent the field is null, and a good card shows a branded placeholder instead of a broken image. See the tools reference for the field shape and the source enum.
The shape you are rendering
// A search_creators row, trimmed to what a card needs.
type AvatarSource = 'discovery' | 'social_account' | 'creatorland_profile'
interface CreatorRow {
handle: string
follower_count?: number
niche?: string
// Paid-plan, null when unavailable. `url` is a stable signed image URL.
avatar?: { url: string; source: AvatarSource } | null
}
The card
The whole trick is avatar?.url. When it is a string, render the image. When the avatar is null (free tier, no entitlement, or simply no image on file), fall back to the placeholder.
function CreatorCard({ row }: { row: CreatorRow }) {
return (
<div className="flex items-center gap-3 rounded-lg border p-3">
{row.avatar?.url ? (
// `avatar.url` is a stable signed URL, safe to use directly in <img>.
<img
src={row.avatar.url}
alt={`${row.handle} avatar`}
width={40}
height={40}
loading="lazy"
className="h-10 w-10 rounded-lg object-cover"
/>
) : (
<AvatarPlaceholder />
)}
<div className="min-w-0">
<div className="font-medium">@{row.handle}</div>
<div className="text-sm text-black-700">
{row.niche ?? 'creator'}
{row.follower_count ? ` · ${row.follower_count.toLocaleString()} followers` : ''}
</div>
</div>
</div>
)
}
The branded placeholder
When avatar is null, show the Creatorland asterisk monogram, a neon-green asterisk on a near-black rounded tile, so the card stays on-brand and never renders an empty box. It is a self-contained inline SVG, no network and no extra asset to ship.
function AvatarPlaceholder({ size = 40 }: { size?: number }) {
return (
<span
aria-hidden
style={{ width: size, height: size }}
className="inline-flex flex-shrink-0 items-center justify-center rounded-lg bg-black-900"
>
<svg viewBox="0 0 16 16" className="h-1/2 w-1/2" fill="#C8F996">
<path d="M2.12727 13.8521L0 10.157L4.30909 8.0091L0 5.843L2.14545 2.1479L6.16364 4.80546L5.85455 0H10.1273L9.83636 4.80546L13.8545 2.1479L16 5.843L11.6727 7.9909L15.9818 10.157L13.8364 13.8521L9.81818 11.1763L10.1273 16H5.85455L6.14545 11.1763L2.12727 13.8521Z" />
</svg>
</span>
)
}
Map a result list to cards
search_creators returns data.results. Render the rows straight through, the null-avatar case is handled per card, so a mixed list (some with avatars, some without) renders cleanly.
const res = await client.callTool({
name: 'search_creators',
arguments: { mode: 'brief', filters: { niche: 'beauty' }, limit: 12 },
})
const { results } = JSON.parse(res.content[0].text).data
return (
<div className="grid gap-2">
{results.map((row) => (
<CreatorCard key={row.handle} row={row} />
))}
</div>
)
That is the whole recipe. The same avatar?.url pattern works for a get_creator_profile response and for lookalike rows, since all three carry the avatar in the same shape.
Keep reading
- Tools reference, the
avatarfield, thesourceenum, and the null contract. - Ground your agent, the end-to-end examples, including the Competitor Roster X-Ray.