Hashtags & Trending — Technical Reference
Where this lives
Backend
apps/backend/graphql/resolvers/hashtag.resolver.js— resolveshashtag,hashtagByName,searchHashtags,trendingHashtags,hashtagStatsapps/backend/graphql/types/hashtag.type.js—HashtagSDL type and query schemaapps/backend/managers/post-managers/hashtag.manager.js— trending score calculation, search, and lookup logicapps/backend/data-access-services/post/hashtag.access-service.js— hashtag DB access (moved underdata-access-services/post/along with the other post-related access services)apps/backend/graphql/resolvers/user-mentions-tags.resolver.js— implementsusersMentionedBy,usersIMentioned,removeTag,removeMention(the monolithicuser.resolver.jsthis doc previously pointed at no longer exists — the user resolvers were split into many files)- Verified but worth flagging:
post-hashtag.access-service.js(nowdata-access-services/post/post-hashtag.access-service.js) andconversation-hashtag.access-service.js(nowdata-access-services/message/conversation-hashtag.access-service.js) are wired —post.manager.js#createPostlinks each extracted hashtag via the former (this doc previously said neither was called by any manager — corrected). Separately,removeTag's underlyingmentions-tags.manager.jsmethodsgetUsersMentionedBy/getUsersIMentionedare still TODO/mock stubs that return empty data regardless of input (removeTagitself was fixed this session — see checklist below).myMentions,mentionsInContent,mentionUser, andtagUserno longer exist in the schema at all (removed — see checklist below), so ignore any references to them below as historical.
Frontend
apps/frontend-nextjs/src/page-components/HomePage.tsx— mobile search overlay queriessearchHashtagsalongside user searchapps/frontend-nextjs/src/app/hashtag/[tag]/page.tsx+HashtagPage.tsx— queriespostsByHashtagto render a hashtag's post grid, plustrendingHashtags(the "Trending" chips row) andhashtagByName/hashtagStats(the header's total posts / distinct people)apps/frontend-nextjs/src/components/PostCard.tsx— renders captions vialinkifyText()(inlib/linkifyHashtags.tsx), which links both#tagsegments to/hashtag/[tag]and@usernamementions to the user's profile; the older hashtag-onlylinkifyHashtags()export from the same file is still used byPublicPostCard.tsx,PublicPostViewer.tsx, andClipsPage.tsx
Technical implementation checklist
-
trendingHashtags— wired inhashtag.resolver.js, callshashtag.manager.js#getTrendingHashtags; consumed byHashtagPage.tsx's "Trending" chips row (this doc previously said there was no frontend caller — corrected) -
searchHashtags— wired inhashtag.resolver.js; consumed byHomePage.tsx's mobile search overlay - Hashtag auto-extraction on post creation —
post.manager.js:217callshashtagManager.extractAndCreateHashtags; combined withgetByHashtag/postsByHashtag, this backs the real/hashtag/[tag]feed (HashtagPage.tsx) and the clickable hashtag linksPostCard.tsxrenders vialinkifyText— see Posts & Feed -
hashtagByName/hashtagStats— wired inhashtag.resolver.js; consumed byHashtagPage.tsx's header (total posts / distinct people) — the hashtag feed's post grid itself still usespostsByHashtag, not these two (this doc previously said neither had a frontend caller — corrected) -
PostHashtag(posts) andConversationHashtag(in-chat) are both wired —post.manager.js#createPostlinks each extracted hashtag viapost-hashtag.access-service.js;message.manager.js#createMessageextracts#hashtagsfrom message text, links them to the conversation viaconversation-hashtag.access-service.js(de-duplicated) and bumps usage;hashtag.access-service.js#getTrendingcounts recent conversation-hashtag links alongside post links, so in-chat hashtags surface in trending too. -
usersMentionedBy/usersIMentioned— wired inuser-mentions-tags.resolver.js; consumed bySettings → Mention history(MentionHistoryPage.tsx, at/settings/mentions) — but the underlyingmentions-tags.manager.jsmethods are still TODO/mock stubs that always return an empty result, so the tabs render and query correctly but never show real mention data -
removeTag— Fixed this session, two bugs: (1) the resolver used to destructure a non-existenttagIdargument, so every call threw regardless of input — it now reads the realuserId/contentType/contentIdargs the SDL declares and returns the real boolean from the manager. (2)mentions-tags.manager.js#removeTagwas a stub (console.log, no DB write) — it now does a realPostMentiondeletion, gated toPOSTcontent only, and authorized to either the tagged user or the post's owner (looked up viapostAccessService.findById); anyone else getspermissions.cannot_remove_tag.removeMention(mentionId)remains a separate, simpler mutation for the same underlyingPostMentionrow, backed bypost-mention.manager.js#deleteMention. -
myMentions/mentionsInContent/mentionUser/tagUser— removed from thementions-tags.type.jsSDL. They were dead fields with no resolver and no frontend consumer, duplicating concepts already covered by the realPostMention-based tagging system (tap-to-tag + caption@mentions, recorded viapost-mention.access-service.js), so they were deleted rather than implemented to keep the schema honest.
Hashtag model
| Field | Description |
|---|---|
name | Original hashtag (mixed case) |
normalizedName | Lowercase normalized version |
usageCount | Total usage count |
trendingScore | Calculated trending score |
lastUsedAt | Most recent usage timestamp |
Queries
trendingHashtags returns the top-N hashtags sorted by trendingScore, which combines recent velocity with overall usage. Use it for the Explore page's trending row.
searchHashtags autocompletes a partial hashtag string — call it on every keystroke as the user types # in the post composer. Results are sorted by usageCount.
hashtagByName fetches a single hashtag by its exact name — useful when navigating to a hashtag page (e.g. /hashtag/photography).
hashtagStats returns the total number of posts and unique users that have used a hashtag — displayed on the hashtag detail page.
query TrendingHashtags($limit: Int) {
trendingHashtags(limit: $limit) {
id name usageCount trendingScore lastUsedAt
}
}
query SearchHashtags($query: String!, $limit: Int) {
searchHashtags(query: $query, limit: $limit) { id name usageCount }
}
query HashtagByName($name: String!) {
hashtagByName(name: $name) { id name usageCount trendingScore }
}
query HashtagStats($hashtagId: ID!) {
hashtagStats(hashtagId: $hashtagId) { totalPosts totalUsers }
}
Hashtags in conversations
Messages can also carry hashtags. The ConversationHashtag table indexes them for in-chat search.
User mentions
myMentions, mentionsInContent, mentionUser, and tagUser do not exist in the schema — they were removed (see checklist above); ignore any older references to them.
removeMention deletes a specific PostMention row and is the mutation that actually works end-to-end — either the post's owner or the mentioned user may call it (post-mention.manager.js#deleteMention). removeTag (userId/contentType/contentId args — contentType must be POST, the only content type tags are modeled for today) also now works end-to-end: it deletes the matching PostMention row, restricted to the tagged user or the post's owner — see the checklist above for the two bugs fixed this session.
# Remove a specific mention (post owner or the mentioned user can call this)
mutation RemoveMention($mentionId: ID!) {
removeMention(mentionId: $mentionId)
}
# Tagged user or the post's owner can call this
mutation RemoveTag($userId: ID!, $contentType: String!, $contentId: ID!) {
removeTag(userId: $userId, contentType: $contentType, contentId: $contentId)
}
Content tags
ContentTag is a system-managed label applied automatically by AI classification — distinct from user-authored hashtags. Tags describe the topic category of posts and conversations.
| Field | Description |
|---|---|
name | Tag label (e.g. photography, sports) |
category | Tag category group |
usageCount | Number of items tagged |
confidenceAvg | Average AI confidence score (0–1) |
Tags are applied to posts via PostContentTag and to conversations via ConversationContentTag. They power the content interest engine — see Posts & Feed for how user affinity is tracked per tag.
Mention history
usersMentionedBy shows which users a given account tends to mention — useful for an analytics or relationship view. usersIMentioned shows who the current user has mentioned recently. Both are consumed by Settings → Mention history (MentionHistoryPage.tsx), but their backing mentions-tags.manager.js implementation is still a TODO/mock stub that always returns an empty list — the tabs are fully wired end-to-end but won't display real mention data until that manager is implemented against PostMention/MessageMention.
query UsersMentionedBy($userId: ID!, $limit: Int) {
usersMentionedBy(userId: $userId, limit: $limit) { id username }
}
query UsersIMentioned($limit: Int) {
usersIMentioned(limit: $limit) { id username }
}