Skip to main content

Tracing & Debugging

FlowCord includes a NavigationTracer that records every menu transition during a session. It is disabled by default and intended for development and debugging.

Enable it in the FlowCord config:

const flowcord = new FlowCord({
client,
enableTracing: true,
});

Once enabled, every navigation event is recorded. Access the events via flowcord.engine.tracer:

const events = flowcord.engine.tracer.events;
console.log(events);
// [
// { from: 'main', to: 'settings', sessionId: '...', userId: '...', timestamp: 1234567890, trigger: 'button:open-settings' },
// { from: 'settings', to: 'permissions', sessionId: '...', userId: '...', timestamp: 1234567891 },
// ]
FieldTypeDescription
fromstringMenu navigated away from
tostringMenu navigated to
sessionIdstringSession identifier
userIdstringDiscord user ID
timestampnumberUnix timestamp (ms)
triggerstring | undefinedComponent that triggered navigation (e.g. 'button:confirm')

Querying paths

tracer.getPathsFrom(menuId) returns all navigation paths originating from a given menu, as arrays of menu names:

const paths = flowcord.engine.tracer.getPathsFrom('main');
// [['main', 'settings', 'permissions'], ['main', 'shop', 'cart']]

This is useful for verifying that your navigation graph matches your intent.

Clearing events

flowcord.engine.tracer.clear();
note

Navigation tracing is in-memory and process-scoped. Events accumulate for the lifetime of the process. Clear them periodically if running the bot for long sessions or testing many flows.


Error handling

Default behaviour

If no onError handler is configured, FlowCord catches unhandled session errors and sends the user an ephemeral dark-red embed with the error message. If the interaction has already been deferred or replied to, it edits the existing reply instead.

Custom error handler

Pass onError to the FlowCord config to override the default:

const flowcord = new FlowCord({
client,
onError: async (session, error) => {
console.error(`[session:${session.id}] Error:`, error);

// Send a custom error message
const interaction = session.commandInteraction;
const embed = new EmbedBuilder()
.setColor('Red')
.setDescription('Something went wrong. Please try again.');

if (interaction.deferred || interaction.replied) {
await interaction.editReply({ embeds: [embed], components: [] });
} else {
await interaction.reply({ embeds: [embed], components: [], ephemeral: true });
}
},
});

session is the MenuSession instance — you can access session.id and other session properties for logging context.


Common errors

Menu "x": must call either setEmbeds() or setLayout() The builder's .build() was called without setting a render callback. Every menu must have either .setEmbeds() or .setLayout().

Menu "x": cannot use both setEmbeds() and setLayout() Both render modes were called on the same builder. Pick one — they are mutually exclusive per menu.

Menu "x": select menus cannot be used with button pagination .setSelectMenu() and .setButtons(..., { pagination }) were both set. Remove one or restructure the flow.

Button "x" is configured as a modal trigger but no matching modal was found A button has opensModal: 'some-id' but .setModal() does not return a modal config with id: 'some-id'. Check that the IDs match exactly.

Button "x" used openModal() action after the interaction was deferred The openModal() action was used in a button that was already deferred. Use opensModal on the button config instead — FlowCord uses it to call showModal() before the interaction is deferred.


Session internals

For a deeper look at how sessions work — the render-await-dispatch loop, in-memory session lifecycle, and what that means for bot restarts and scalability — see ARCHITECTURE.md in the flowcord-core repository.