Package
custom-id v0.3.0

About
Discord gives a component one string to carry its state, capped at 100 characters. @seedcord/custom-id declares what that string holds, packs values into it, and reads them back with the types you declared. The component that mints the id and the code that reads a click share one declaration.
Each id also carries a short hash of its fields. A click on a component built before you changed them throws on decode, so values in the old layout never reach your code.
Both seedcord transports depend on it, and a seedcord bot imports CustomId from its transport. Install it directly to use it with discord.js or any other Discord library, without seedcord. You then match each click to its definition yourself.
seedcord is pre-1.0, so a minor version can break your bot. Each release's changelog says what changed and how to update.
Installation
pnpm add @seedcord/custom-idUsage
import { ButtonBuilder, Events } from 'discord.js';
import { CustomId } from '@seedcord/custom-id';
const Approve = new CustomId('approve').snowflake('userId').oneOf('action', ['approve', 'deny']);
new ButtonBuilder().setCustomId(Approve.encode({ userId: '123', action: 'deny' })).setLabel('Deny');
client.on(Events.InteractionCreate, (interaction) => {
if (!interaction.isButton()) return;
if (!Approve.owns(interaction.customId)) return;
const { userId, action } = Approve.decode(interaction.customId);
// userId: string, action: 'approve' | 'deny'
});The custom ids guide covers every field type and what decode throws. The reference has the full API.