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.
- 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
- 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
Install from npm:
npm install convex-smart-tags
# or
yarn add convex-smart-tags- Add the component to your Convex app configuration:
// 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;- Use the client wrapper inside Convex functions:
// functions.ts (Convex server-side code)
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.
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.
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.
Basic tag management:
// Add a tag to an entity
await smartTags.addTag(ctx, {
tagName: "important",
tableName: "posts",
entityId: "post123",
});
// Get tags for an entity
const tags = await smartTags.getTagsForEntity(ctx, {
tableName: "posts",
entityId: "post123",
});Tag hierarchy:
// Create a parent-child relationship
await smartTags.createTagHierarchy(ctx, {
parentTag: "technology",
childTag: "ai",
});
// Get child tags
const children = await smartTags.getChildTags(ctx, { parentTag: "technology" });
// Get ancestor chain
const ancestors = await smartTags.getTagAncestors(ctx, { tagName: "ai" });Analytics and trending:
// Get trending tags for the last 7 days
const sevenDays = 7 * 24 * 60 * 60 * 1000;
const trending = await smartTags.getTrendingTags(ctx, {
timeRange: sevenDays,
limit: 10,
});
// Get comprehensive statistics for a single tag
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;The package includes test helper registration for the Convex test environment.
// src/test.ts — usage inside tests
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).
To develop locally:
- Install dependencies
npm install- Build and run example
npm run build
cd example
npm install
npx convex dev
npm run dev- Typecheck and run tests
npm run typecheck
npm run testApache-2.0 — see the LICENSE file for details.