Convex Smart Tags Component
A reusable Convex component that provides intelligent tagging and categorization capabilities with hierarchical organization and analytics. Designed for easy integration into Convex projects and to work across multiple tables and entity types.
Why use this component
- Core tag management (add, remove, query tags) across table boundaries
- Tag hierarchy support (parent-child relationships, ancestor/descendant lookups)
- Tag analytics and trending (daily counts, trending calculation)
- Type-safe TypeScript client wrapper for convenient usage in Convex functions
- Example app and test helpers included for fast onboarding
Features
- Add and remove tags from any entity
- Query tags for an entity and entities by tag
- Create and query parent-child tag relationships
- Track daily tag usage and compute trending tags
- Cross-table tag usage statistics
- Full TypeScript types for client and server integration
Installation
Install from npm:
npm install convex-smart-tags
yarn add convex-smart-tags
Quick start
- Add the component to your Convex app configuration:
import { defineApp } from "convex/server";
import smartTags from "convex-smart-tags/convex.config";
const app = defineApp();
app.use(smartTags);
export default app;
- Use the client wrapper inside Convex functions:
import { SmartTags } from "convex-smart-tags";
import { components } from "./_generated/api";
import { mutation, query } from "./_generated/server";
const smartTags = new SmartTags(components.smartTags);
export const tagPost = mutation({
handler: async (ctx) => {
const postId = "post123";
await smartTags.addTag(ctx, {
tagName: "urgent",
tableName: "posts",
entityId: postId,
});
},
});
export const getPostTags = query({
args: { postId: v.id("posts") },
handler: async (ctx, args) => {
return smartTags.getTagsForEntity(ctx, {
tableName: "posts",
entityId: args.postId,
});
},
});
See the example/ directory for a complete working example application.
API overview
The component exposes a client wrapper SmartTags with a type-safe surface. Primary methods include:
- addTag(ctx, args: { tagName, tableName, entityId }): Promise
- removeTag(ctx, args: { tagName, tableName, entityId }): Promise
- getTagsForEntity(ctx, args: { tableName, entityId }): Promise<Array>
- getEntitiesByTag(ctx, args: { tagName, tableName?, limit? }): Promise<Array<{ tableName, entityId }>>
- createTagHierarchy(ctx, args: { parentTag, childTag }): Promise
- getChildTags(ctx, args: { parentTag, limit? }): Promise<Array<{ tagName, level }>>
- getTagAncestors(ctx, args: { tagName }): Promise<Array<{ tagName, level }>>
- getTrendingTags(ctx, args: { timeRange, limit? }): Promise<Array<{ tagName, usageCount, trend }>>
- getTagStats(ctx, args: { tagName }): Promise<{ totalUsage, entityCount, createdAt, recentTrend }>
- getTagsAcrossTables(ctx, args?: { limit? }): Promise<Array<{ tableName, tagName, entityCount }>>
Each method is implemented in a type-safe manner; consult the generated types under src/_generated in your project for full signatures.
Schema
The component registers the following tables in your Convex schema:
-
tags
- name: string
- createdAt: number
- usageCount: number
- indexed by: by_name
-
entityTags
- tagName: string
- tableName: string
- entityId: string
- createdAt: number
- indexes: by_tag, by_entity, by_tag_and_entity
-
tagHierarchy
- parentTag: string
- childTag: string
- createdAt: number
- indexes: by_parent, by_child, by_parent_and_child
-
tagAnalytics
- tagName: string
- usageDate: number
- dailyCount: number
The full schema definition is in src/component/schema.ts.
Examples
Basic tag management:
await smartTags.addTag(ctx, {
tagName: "important",
tableName: "posts",
entityId: "post123",
});
const tags = await smartTags.getTagsForEntity(ctx, {
tableName: "posts",
entityId: "post123",
});
Tag hierarchy:
await smartTags.createTagHierarchy(ctx, {
parentTag: "technology",
childTag: "ai",
});
const children = await smartTags.getChildTags(ctx, { parentTag: "technology" });
const ancestors = await smartTags.getTagAncestors(ctx, { tagName: "ai" });
Analytics and trending:
const sevenDays = 7 * 24 * 60 * 60 * 1000;
const trending = await smartTags.getTrendingTags(ctx, {
timeRange: sevenDays,
limit: 10,
});
const stats = await smartTags.getTagStats(ctx, { tagName: "ai" });
Example Convex component registration (from example/convex/convex.config.ts):
import { defineApp } from "convex/server";
import smartTags from "convex-smart-tags/convex.config";
const app = defineApp();
app.use(smartTags);
export default app;
Testing
The package includes test helper registration for the Convex test environment.
import { convextTest } from "convex-test";
import smartTagsTest from "convex-smart-tags/src/test";
const t = convextTest();
smartTagsTest.register(t, "smartTags");
Run the project's test and typecheck commands as configured (see vitest.config.js and package.json scripts).
Development
To develop locally:
npm install
npm run build
cd example
npm install
npx convex dev
npm run dev
npm run typecheck
npm run test
License
Apache-2.0 — see the LICENSE file for details.