Skip to main content

useLumioEvent

useLumioEvent(type) returns the latest payload of one event type, or null until that type arrives. The return type follows the literal event name.

import { Lumio, Text, useLumioEvent } from "@zaflun/lumio-sdk";

function CheerTotal() {
const cheer = useLumioEvent("twitch:cheer");
return cheer ? <Text content={`${cheer.bits} bits`} /> : null;
}

Lumio.render(<CheerTotal />, { target: "layer" });

cheer.bits is a number. The hook returns the payload itself: use cheer.bits, rather than cheer.data.bits. The SDK subscribes to the host's event.platform notification, keeps the latest payload for the requested type, and unsubscribes when the component unmounts or the type changes. It does not replay events that arrived before the component subscribed.

Signature​

function useLumioEvent<K extends LumioEventType>(
eventType: K
): LumioEventPayloadMap[K] | null;

LumioEventType, LumioEventPayloadMap, and LumioEventPayload<K> come from @zaflun/lumio-protocol. Its LUMIO_EVENT_TYPES list is the source for autocomplete; lumioEventPayloadSchema(type) and parseLumioEventPayload(type, value) provide runtime validation when you handle external data. The schemas retain extra provider fields, while typed fields follow the backend's golden event fixtures. Fields present only after enrichment are optional.

For example, twitch:follower has user_name, user_id, and followed_at; avatarUrl may be absent. The field names match the actual event payload, including snake_case where the provider or backend uses it. Your editor shows the fields of each type; the payload types are the reference, so this page does not repeat them.

Delivery​

The host page that renders your extension reads the account's live streams and sends each event to your worker as the event.platform notification { eventType, data }. This happens on every surface: the overlay layer and widget browser source in OBS, the widget editor preview and the designer preview in the dashboard. It happens only while the surface is open.

  • Platform events (Twitch, YouTube, Kick, Spotify, StreamElements): data is the event's stored payload.
  • Chat (chat:message, twitch:chat, youtube:chat, kick:chat): data is the chat message (ChatMessagePayload). Every message arrives twice, once as chat:message and once as its platform's chat type.
  • twitch:ban / twitch:unban: data is the moderation-log entry (ModerationLogPayload).

Your manifest's permissions decide what you receive:

Event typesRequired permission
chat:message, twitch:chat, youtube:chat, kick:chat, twitch:ban, twitch:unbanchat:read
Every other supported event typeevents:read

Without the permission the host does not forward the event, and without either permission it does not open a stream at all.

Twitch reward lifecycle events (twitch:reward_add, twitch:reward_update, and twitch:reward_remove) are live only; they are absent from event history. youtube:gift_membership_received is stored in the event history but never sent live, so an extension surface does not receive it.

Supported event types​

PlatformEvent types
Twitchtwitch:follower, twitch:subscribe, twitch:gift, twitch:resub, twitch:cheer, twitch:raid, twitch:reward, twitch:reward_status, twitch:reward_add, twitch:reward_update, twitch:reward_remove, twitch:hype_train, twitch:hype_train_end, twitch:poll, twitch:poll_end, twitch:prediction, twitch:prediction_lock, twitch:prediction_end, twitch:goal, twitch:goal_end, twitch:ad_break, twitch:ban, twitch:unban, twitch:stream_online, twitch:stream_offline, twitch:stream_update
YouTubeyoutube:subscribe, youtube:member, youtube:superchat, youtube:supersticker, youtube:gift_membership, youtube:gift_membership_received, youtube:poll, youtube:stream_online, youtube:stream_offline
Kickkick:follower, kick:subscribe, kick:resub, kick:gift, kick:kicks, kick:reward, kick:reward_status, kick:stream_online, kick:stream_offline, kick:stream_update
Spotifyspotify:track, spotify:play, spotify:pause, spotify:skip, spotify:volume, spotify:queue_add, spotify:device, spotify:playlist_add, spotify:playlist_remove, spotify:playlist_create, spotify:playlist_edit, spotify:playlist_delete
StreamElementsstreamelements:tip
Chat, per platformtwitch:chat, youtube:chat, kick:chat
Chat, all platformschat:message

Use chat:message when you handle chat the same way on every connected platform.

Example: Follower alert​

Needs events:read.

import { Lumio, Box, Text, useLumioEvent } from "@zaflun/lumio-sdk";
import { useEffect, useState } from "react";

function FollowerAlert() {
const follow = useLumioEvent("twitch:follower");
const [visible, setVisible] = useState(false);

useEffect(() => {
if (!follow) return;
setVisible(true);
const timer = setTimeout(() => setVisible(false), 5000);
return () => clearTimeout(timer);
}, [follow]);

if (!follow || !visible) return null;

return (
<Box style={{ background: "rgba(99, 102, 241, 0.9)", borderRadius: 12, padding: "16px 24px" }}>
<Text content={`New follower: ${follow.user_name}!`} variant="heading" />
</Box>
);
}

Lumio.render(<FollowerAlert />, { target: "layer" });

Example: Now playing (Spotify)​

Needs events:read. The payload is Spotify's now-playing state, as used by the system Spotify Now Playing layer.

import { Box, Image, Text, useLumioEvent } from "@zaflun/lumio-sdk";

function NowPlaying() {
const nowPlaying = useLumioEvent("spotify:track");
if (!nowPlaying?.is_playing) return null;

const { track } = nowPlaying;
return (
<Box style={{ display: "flex", gap: 12, alignItems: "center" }}>
<Image src={track.album_art} alt="Album art" style={{ width: 48, height: 48, borderRadius: 4 }} />
<Box>
<Text content={track.name} />
<Text content={track.artist} variant="muted" />
</Box>
</Box>
);
}

Try it locally​

Run lumio dev and send an event from a second terminal:

lumio trigger twitch:cheer --bits 500

The devkit's mock host builds the remaining fields from a fixture and validates the result before delivery. Its event catalog marks the types the host does not forward to extension surfaces.

Notes​

  • shopify:order and shopify:order_paid are not currently in LumioEventType, so this hook cannot subscribe to them. If you handle a Shopify order payload through another extension or automation surface, treat raw.customer_display as untrusted buyer text and render it as text, never HTML. The buyer label, raw.line_items, and raw.total_price can each be absent according to consent and the merchant's settings.
  • The hook returns a new object for every event, so use it as a useEffect dependency to react to each one.
  • Declare events:read and/or chat:read in your manifest's permissions, or the hook never fires (see Delivery).
  • Events from before the surface opened, or while it was closed, are not replayed.