Custom Events
Track business-specific events beyond the automatic captures using track(), captureError(), and other SDK methods.
track(name, props?)
The primary method for recording custom events. Events are queued, batched, and delivered asynchronously -- track() never throws.
analytics.track('purchase', { sku: 'pro', amount: 42 });analytics.track("purchase", {"sku": "pro", "amount": 42})Basic Examples
// Simple event with no properties
analytics.track('daily_reward_claimed');
// Event with structured properties
analytics.track('ticket_opened', {
category: 'billing',
priority: 'high',
guildId: interaction.guildId,
});
// Tracking a game result
analytics.track('game_finished', {
game: 'trivia',
winner: interaction.user.id,
playerCount: 4,
durationMs: 120000,
});
// Feature usage tracking
analytics.track('setting_changed', {
setting: 'language',
oldValue: 'en',
newValue: 'ko',
});# Simple event with no properties
analytics.track("daily_reward_claimed")
# Event with structured properties
analytics.track("ticket_opened", {
"category": "billing",
"priority": "high",
"guild_id": interaction.guild_id,
})
# Tracking a game result
analytics.track("game_finished", {
"game": "trivia",
"winner": interaction.user.id,
"player_count": 4,
"duration_ms": 120000,
})
# Feature usage tracking
analytics.track("setting_changed", {
"setting": "language",
"old_value": "en",
"new_value": "ko",
})Properties (props)
Props are arbitrary key-value pairs attached to the event. They appear in the dashboard under Activity > Custom Events and can be used for filtering and grouping in the Explore tool.
Note
- Use
string,number, andbooleanvalues for best filterability. - Avoid deeply nested objects -- flat structures are easier to query.
- Use consistent key names across events for meaningful cross-event analysis.
Limits
| Item | Limit |
|---|---|
| Event name length | 1 -- 256 characters |
| Props keys per event | Max 50 |
| Key length | Max 100 characters |
| Total props size | Max 8 KiB serialized |
| Individual string value | Max 4 KiB |
Note
Event names are limited to 256 characters and props must be JSON-serializable. Events that exceed limits are silently truncated or dropped -- track() never throws.
captureError(err, ctx?)
Explicitly report an error. Unlike command_error events (which are auto-captured when interaction replies reject), captureError is for errors you catch yourself.
try {
await riskyDatabaseOperation();
} catch (err) {
analytics.captureError(err, {
userId: interaction.user.id,
guildId: interaction.guildId,
operation: 'database_query',
});
}try:
await risky_database_operation()
except Exception as exc:
analytics.capture_error(exc, {
"user_id": interaction.user.id,
"guild_id": interaction.guild_id,
"operation": "database_query",
})Error fields are size-capped: name (200 chars), message (2 KiB), stack (8 KiB). The source field is set to captureError to distinguish from auto-captured errors.
The optional context object supports special keys:
| Key | Effect |
|---|---|
guildId | Stored as the event's guildId dimension (filterable) |
channelId | Stored as the event's channelId dimension |
userId | Stored as the event's userId dimension |
| Other keys | Stored in data.context as additional metadata |
flush()
Force-sends all queued events immediately.
await analytics.flush();await analytics.flush()INFO
Normally unnecessary. The SDK auto-flushes every 5 seconds (configurable via flushIntervalMs) or when 50 events are queued. Use flush() only when you need to guarantee delivery before a specific point in time.
shutdown()
Detaches all listeners, clears timers, and performs a final flush with a 3-second timeout.
await analytics.shutdown();await analytics.shutdown()The SDK auto-registers SIGINT, SIGTERM, and beforeExit handlers that trigger a flush, so explicit shutdown() calls are usually not needed. Call it manually when you want to detach and re-attach the SDK to a different client, or during graceful shutdown sequences.
Viewing Custom Events in the Dashboard
Custom events appear in several places:
- Activity > Custom Events -- dedicated report page with trend charts and a table of recent events.
- Explore -- filter by
event_type = customand drill down bydata.nameor anyprops.*field. - Overview -- custom event counts contribute to the total event KPI.
Filtering by Props in Explore
In the Explore tool, custom event properties are accessible under props.* field paths:
event_type = custom
props.category = billing
props.priority = highSee Explore for more on the query builder.
Complete Example
import { Client, GatewayIntentBits } from 'discord.js';
import { createDicolytics } from '@dicolytics/discord.js';
const client = new Client({
intents: [GatewayIntentBits.Guilds],
});
const analytics = createDicolytics(client, {
apiKey: process.env.DICOLYTICS_KEY!,
});
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
// Track command usage with business context
analytics.track('command_used', {
command: interaction.commandName,
guildId: interaction.guildId,
channel: interaction.channelId,
isPremium: await checkPremiumStatus(interaction.guildId),
});
// Handle the command...
});
// Capture errors explicitly
process.on('unhandledRejection', (err) => {
analytics.captureError(err, { source: 'unhandledRejection' });
});
client.login(process.env.DISCORD_TOKEN);import os
import discord
from dicolytics import create_dicolytics
bot = discord.Bot()
analytics = create_dicolytics(bot, api_key=os.environ["DICOLYTICS_KEY"])
@bot.event
async def on_application_command(ctx):
# Track command usage with business context
analytics.track("command_used", {
"command": ctx.command.name,
"guild_id": str(ctx.guild_id),
"channel": str(ctx.channel_id),
"is_premium": await check_premium_status(ctx.guild_id),
})
@bot.event
async def on_error(event, *args, **kwargs):
import sys
analytics.capture_error(sys.exc_info()[1], {"source": "on_error"})
bot.run(os.environ["DISCORD_TOKEN"])