Account and Operations

Reactions, views and engagement data

Updated 89 views

Kookee gives you three kinds of engagement signal: emoji reactions, view counts, and votes on feedback posts. Reactions and views work with your API key, so they run client-side.

Emoji reactions

Available on blog posts, changelog entries and help articles. Five reaction types come built in:

fire 🔥 · heart ❤️ · rocket 🚀 · eyes 👀 · mindblown 🤯

The API also accepts reaction types of your own: any name of up to 50 lowercase letters, digits and underscores. The helpful vote on help articles uses yes and no this way.

TypeScript
await kookee.blog.react(postId, { reactionType: 'fire', action: 'add' });
await kookee.blog.react(postId, { reactionType: 'fire', action: 'remove' });

await kookee.changelog.react(entryId, { reactionType: 'heart', action: 'add' });
await kookee.help.react(articleId, { reactionType: 'rocket', action: 'add' });

// Any entry type, by id — what the three helpers above call underneath
await kookee.entries.react(entryId, { reactionType: 'eyes', action: 'add' });

Current counts come back on the entry as a Record<string, number>:

TypeScript
post.reactions; // { fire: 12, heart: 3 }

Each of these takes the entry id, not the slug, and returns the updated counts. entries.react is the generic form: any entry type by id, including pages, which have no module helper of their own.

Tracking what the user reacted with

The API stores counts, not per-user state — so your UI has to remember what this visitor already clicked, or the toggle will not work.

In React, Reactions from @kookee/react/entry/client does all of this for you: it remembers what the visitor pressed, updates the count at once and rolls it back if the request fails:

tsx
<Reactions entryId={post.id} reactions={post.reactions} config={{ apiKey: 'your-api-key' }} />

The manual version, for other frameworks:

TypeScript
const STORAGE_KEY = 'kookee-reactions';

function getMyReactions(entryId: string): string[] {
  const all = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}');
  return all[entryId] ?? [];
}

async function toggleReaction(entryId: string, reactionType: string) {
  const mine = getMyReactions(entryId);
  const has = mine.includes(reactionType);

  await kookee.blog.react(entryId, {
    reactionType,
    action: has ? 'remove' : 'add',
  });

  const all = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}');
  all[entryId] = has ? mine.filter((r) => r !== reactionType) : [...mine, reactionType];
  localStorage.setItem(STORAGE_KEY, JSON.stringify(all));
}

Local storage is per-browser, so the same person on a phone can react again. For accurate per-user data, store reactions against your own user records and call Kookee from your server.

Update the count optimistically in your UI — waiting for a round trip before a number changes makes the button feel broken.

Views

View counts are tracked automatically on blog posts, pages and help articles — no call required:

TypeScript
article.views; // number

Views count fetches, which includes crawlers and your own preview traffic. Treat them as relative rather than absolute: comparing two articles is meaningful, a single number is not.

Feedback votes

Votes on feedback posts are cast on your hosted portal, where visitors sign in to vote. They come back through the API on every post as voteCount, and sort: 'top' orders the board by them:

TypeScript
const { data } = await kookee.feedback.list({ sort: 'top', limit: 20 });
data[0].voteCount;

There is no public endpoint for casting a vote; that happens on the portal so each person counts once.

Reading the signals

Reactions on changelog entries are the cheapest product feedback available. Ship ten things a month, see which two collected everything, and spend the next month there. It costs a user one click instead of a survey.

High views with few reactions on a blog post usually means the title promised something the body did not deliver. Low views with strong reactions means a discovery problem — the post works, nobody arrives at it.

For help articles, views and chat conversations are the signal. An article nobody reaches is a navigation or search-wording problem — see Writing content in the dashboard on opening an article in the reader's own words. An article everyone reaches but that keeps generating the same support email is a content problem, and the email is your feedback loop. Reactions work on help articles too, but on documentation they measure less than the questions people ask about it: the No article cited filter in Read what visitors asked your assistant is the sharper instrument. If you do collect them, a "Was this article helpful?" vote asks the question directly: HelpfulVote from @kookee/react/help/client stores the answers as the article's yes and no reactions.

Feedback votes tell you what your most engaged users want, which is a real signal and not the same as what your whole user base needs. Use them to find the things you did not know about, not to outsource prioritization.

Where to put the controls

Reaction buttons belong at the end of a post, where someone has finished reading and formed an opinion. Keep the five types — a reaction bar with twenty options gets fewer clicks than one with five.

Was this article helpful?