Queries
viewer
Returns the currently authenticated user’s profile.
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
const user = useQuery(api.users.viewer);
Returns: User | null
OAuth provider avatar URL
User-uploaded custom avatar (takes precedence over image)
Total experience points earned
Current player level (based on XP)
Number of completed quests
Current daily login streak
Last active date in YYYY-MM-DD format (for streak calculation)
plan
'FREE' | 'PRO' | 'ENTERPRISE'
User’s subscription tier
Preferred programming language (e.g., “javascript”, “python”)
Last activity timestamp (updated via heartbeat)
Whether this is a guest account
getRank
Returns the user’s global leaderboard rank based on XP.
const rank = useQuery(api.users.getRank);
// Returns: 42 (user is ranked #42)
Returns: number
Rank is calculated by counting users with higher XP. Anonymous users are excluded from rankings.
export const getRank = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) return 0;
const user = await ctx.db.get(userId);
if (!user || user.xp === undefined) return 0;
const betterUsers = await ctx.db
.query("users")
.withIndex("by_xp", q => q.gt("xp", user.xp!))
.filter(q => q.neq(q.field("isAnonymous"), true))
.collect();
return betterUsers.length + 1;
}
});
getTopUsers
Returns the top players on the global leaderboard.
Maximum number of users to return (default: 50)
const topPlayers = useQuery(api.users.getTopUsers, { limit: 10 });
Returns: Array<TopUser>
Player level (defaults to 1)
searchUsers
Search for users by name or email (for sending friend requests).
Search query (minimum 2 characters)
const results = useQuery(api.users.searchUsers, { query: "john" });
Returns: Array<SearchResult> (max 5 results)
Search excludes the current user and anonymous users. Returns up to 5 matches.
getRecentBadges
Returns the user’s 3 most recently unlocked badges.
const badges = useQuery(api.users.getRecentBadges);
Returns: Array<Badge>
User who earned the badge
Badge identifier (e.g., “hello-world”, “streak-5”)
Timestamp when badge was earned
Badge metadata with title and icon fields
Available Badges
const BADGES_META = {
'hello-world': { title: '"Hello World" Master', icon: 'BookOpen' },
'streak-5': { title: '5 Day Streak', icon: 'Flame' },
'bug-hunter': { title: 'Bug Hunter', icon: 'Bug' },
'algo-architect': { title: 'Algo Architect', icon: 'Cpu' },
'css-wizard': { title: 'CSS Wizard', icon: 'Palette' },
};
Mutations
updateName
Update the user’s display name.
const updateName = useMutation(api.users.updateName);
await updateName({ name: "CodeMaster" });
updateImage
Update the user’s custom avatar.
Avatar URL or base64-encoded image
const updateImage = useMutation(api.users.updateImage);
await updateImage({ image: "https://example.com/avatar.png" });
updatePreferredLanguage
Set the user’s preferred programming language.
Language identifier (e.g., “javascript”, “python”, “cpp”)
const updateLang = useMutation(api.users.updatePreferredLanguage);
await updateLang({ language: "typescript" });
heartbeat
Update the user’s last seen timestamp to track presence.
const heartbeat = useMutation(api.users.heartbeat);
// Call periodically (e.g., every 30 seconds)
setInterval(() => heartbeat(), 30000);
Call this mutation periodically to keep the user’s online status up-to-date.
awardBadge
Award a badge to the current user (typically called after completing achievements).
Badge identifier to award
const awardBadge = useMutation(api.users.awardBadge);
await awardBadge({ badgeId: "hello-world" });
If the user already has the badge, this operation is idempotent (no duplicate badges).
Example: Complete Profile Component
import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
function UserProfile() {
const user = useQuery(api.users.viewer);
const rank = useQuery(api.users.getRank);
const badges = useQuery(api.users.getRecentBadges);
const updateName = useMutation(api.users.updateName);
if (!user) return <div>Please sign in</div>;
return (
<div>
<img src={user.customAvatar || user.image} alt={user.name} />
<h1>{user.name}</h1>
<p>Level {user.level} • {user.xp} XP</p>
<p>Global Rank: #{rank}</p>
<p>Streak: {user.streak} days 🔥</p>
<div>
<h2>Recent Badges</h2>
{badges?.map(badge => (
<div key={badge._id}>
{badge.meta.title} {badge.meta.icon}
</div>
))}
</div>
<button onClick={() => updateName({ name: "New Name" })}>
Update Name
</button>
</div>
);
}