fluxcord
v0.1.0
Published
Session-based Components V2 UI framework for Discord bots: flows, screens, TSX views.
Maintainers
Readme
Stateful, multi-screen Discord UIs in TSX. Built on discord.js v14 (Components V2).
A screen is a plain function of state: when a click changes that state through a handler you wrote, the framework re-renders the screen and edits the message, so you never touch edits or component ids yourself. fluxcord was extracted from a production Discord bot, where it runs every admin panel today.

Features
- Every mounted panel becomes a session that owns its state. Clicks on one panel run one at a time through a FIFO queue, so handlers never race each other, and idle sessions are swept on an interval.
- Flows carry as many screens as you want, and navigation targets such as
ui.go('history')are checked against the screens map at compile time. - Screens are authored in TSX through a custom JSX runtime, or with plain builder functions, and both compile to the same trees.
- Buttons bind to handler functions by identity, so there are no custom-id strings to parse, and every committed change re-renders the screen and edits the message.
- Permission gates are declared on a flow or on a single control, and the policy port you wire at boot decides every click.
- An optional rehydrate store lets open panels survive a restart.
- The core entry is platform-free; the discord.js binding lives behind a
peer-dependent
fluxcord/discordentry.
Requirements
- Node 22 or newer
- discord.js 14.25 or newer, installed as a peer
- TypeScript 5 or newer, for TSX authoring
Install
npm install fluxcord discord.jsFor TSX authoring, point jsxImportSource at the package:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "fluxcord",
"module": "Node16",
"moduleResolution": "Node16"
}
}Example
A counter written as a flow:
import { action, screen, flow } from 'fluxcord';
interface CounterData {
count: number;
}
const plus = action<CounterData>()((event) => {
event.mutate((data) => {
data.count += 1;
});
});
const minus = action<CounterData>()((event) => {
event.mutate((data) => {
data.count -= 1;
});
});
const counterScreen = screen<CounterData>()((data, { Button }) => (
<view>
<text>Count: {data.count}</text>
<row>
<Button onClick={minus} label="-1" secondary />
<Button onClick={plus} label="+1" />
</row>
</view>
));
export const counterFlow = flow<CounterData>('counter', {
screens: { main: counterScreen },
first: 'main',
initialData: { count: 0 },
});Here the two action functions are the entire click surface: a click runs
one of them, event.mutate applies the change, and the framework handles
the re-render and the message edit. Buttons bind to handlers by identity
instead of through id strings, so there is nothing to parse and nothing
that can drift out of sync, and the state lives in a session the framework
tracks rather than in a Map you babysit.
Documentation
You'll find full documentation in docs/. The guide walks you from an empty folder to a running bot; since it is written directly from the example bot in examples/, every snippet comes from code that actually builds and runs.
Contributing
fluxcord is young and maintained by one person. Bug reports with a minimal reproduction are the most useful thing right now. Before filing one, check KNOWN_ISSUES.md in case it is already on the list. For anything larger, open an issue first so the scope can settle before code arrives.
Roadmap
Version goals and their checkboxes live in ROADMAP.md, and notable changes are tracked in CHANGELOG.md.
Released under the MIT license.
Support the project on GitHub Sponsors.
