jChat+ puts Twitch, Kick, and YouTube chat into one browser-source overlay for your stream. Use the setup page to choose your channels and appearance, preview the result, and get a ready-to-use URL.
Use any platform on its own or combine them. Twitch is optional.
https://chat.melkepakken.tv/- Open the jChat+ setup page.
- Choose your platforms and customize the overlay's appearance.
- Copy the generated URL into a Browser Source in OBS, XSplit, Meld, Streamlabs Desktop, or similar streaming software.
That's it. You do not need to set up Cloudflare or edit query parameters for normal hosted use.
- Twitch + Kick + YouTube chat support in one overlay
- Independent platform controls and optional native Twitch chat GIFs
- BTTV, FFZ, and 7TV emotes
- 7TV name paints, user badges, and cosmetics
- Twitch, Kick, and YouTube platform badges, shown by default
- YouTube custom emoji and a choice of native emoji or pinned Twemoji
- Fonts, text sizes, username colours, strokes, shadows, and fitted message backgrounds
- Bot, command, and username filtering
- Smooth message animation and optional fading
- A setup page with live preview and simulated messages
!reloadchatsupport for Twitch moderators and Kick broadcasters/moderators
jChat+ supports Twitch chat, Twitch badges and cheermotes, custom channel badges, and BTTV, FFZ, and 7TV emotes. It also supports 7TV paints and user cosmetics, plus !reloadchat from Twitch moderators.
Show GIFs displays native Twitch chat GIFs inside their message. Choose Small (160px wide), Medium (240px), or Large (360px); all use a 3:2 media area and shrink to fit narrow overlays. Medium is the default and matches the original 240 × 160 size. GIFs are off by default. When off, or a GIF cannot load, the original caption stays visible. Animated emotes and 7TV animations are unaffected. This option only changes your overlay; it does not change Twitch channel settings or who can send GIFs. The current 7TV name-paint integration uses Twitch identities.
jChat+ can find a Kick chatroom from its channel name, or use a manually supplied chatroom ID. It supports:
- Live chat messages and Kick emotes
- Deleted messages
- Broadcaster/moderator
!reloadchat - Kick role badges and global level badges
- Custom subscriber badges when they are available in the channel data
Known limitation: Gift badge tiers are not fully mapped. jChat+ uses a default fallback gift badge unless Kick sends a direct image URL.
Kick connections use the /api/kick/realtime Pages Function to discover Kick's current realtime provider. The overlay supports Centrifugo and Pusher and renews anonymous guest tokens automatically. Centrifugo traffic passes through /api/kick/socket because Kick rejects third-party browser origins; Pusher connects directly. The relay does not store or log chat messages. No Kick account login or API credentials are required. These are Kick website endpoints rather than its documented public API, so upstream changes can still affect compatibility.
jChat+ can find a public live broadcast from a YouTube handle or channel URL. You can also connect directly with a video ID or URL, which is required for unlisted streams and keeps initial priority.
Automatic handle discovery checks YouTube's public live listings. A confirmed offline channel waits about one minute to several minutes between checks and does not use the YouTube Data API. If the public listings cannot confirm a result, discovery falls back to the existing Data API checks. Videos already found are verified before searching again, and repeated failed searches wait progressively longer. A new public broadcast may take a few minutes to appear, or longer if both discovery methods are unavailable. This does not slow polling after Live Chat is connected. A direct-only youtube_video configuration bypasses handle discovery. With a fallback youtube handle, repeated generic direct-chat failures may trigger a check for a different current public live stream; an offline handle does not abandon the direct video. Daily Data API quota cooldowns do not stop regular public-listing checks.
If automatic discovery reports a channel unavailable, jChat+ stops checking that handle until the overlay is reloaded; direct-video retries continue independently.
Supported YouTube features include:
- Explicit Live Chat, with no silent fallback to Top Chat
- Text messages, YouTube custom emoji, and ordinary Unicode emoji
- Deterministic username colours
- Moderator message deletion and user message retraction
- Bot, command, and blocked-user filtering
- Retry, session recovery, and reconnection handling
- Automatic return to public handle discovery after a direct stream ends when a fallback channel is configured
YouTube badges, membership styling, Super Chat styling, Super Stickers, and profile pictures are planned for a later update and are not currently supported.
Current limitation: YouTube chat follows the polling cadence returned by YouTube. jChat+ paces larger message batches for smoother mixed-platform chat, but messages may still arrive in bursts or appear behind the visible web chat.
This section is optional. The setup page generates these URLs for you, but you can edit them manually when you want more control.
Append one of these examples to https://chat.melkepakken.tv/v2/:
| Platforms | Query |
|---|---|
| Twitch | ?channel=creator |
| Kick | ?twitch=false&kick=creator |
| YouTube | ?twitch=false&youtube=CreatorTV |
| Twitch + Kick | ?channel=creator&kick=creator |
| Twitch + YouTube | ?channel=creator&youtube=CreatorTV |
| Kick + YouTube | ?twitch=false&kick=creator&youtube=CreatorTV |
| Twitch + Kick + YouTube | ?channel=creator&kick=creator&youtube=CreatorTV |
Existing channel=creator URLs still enable Twitch. twitch=false overrides that channel, ignoring case. With no channel, Twitch stays off; standalone Kick and YouTube URLs also work without twitch=false. No usable sources leaves an empty overlay.
Legacy kick=true still reuses the configured Twitch name, even with twitch=false. Its aliases are 1, yes, same, channel, and twitch; kick_channel remains an alternative to kick, and kick_room remains a room override. Legacy youtube=true (also 1, yes, same, channel, or kick) prefers a specific configured Kick name, otherwise the configured Twitch name. false, 0, no, off, and disabled are never used as source names; youtube=false also disables a supplied video.
The setup page resolves blank Kick fields from enabled Twitch, and blank YouTube fields from enabled Kick or Twitch. Disabled fields are excluded. A YouTube video with a blank handle stays direct-only.
For a real name that matches a same-channel alias, use an explicit form such as kick=@twitch or youtube=@kick. The generator adds @ when needed.
https://chat.melkepakken.tv/v2/?twitch=false&youtube_video=VIDEO_IDReplace VIDEO_ID with the actual video ID or a YouTube video URL. This mode does not perform handle discovery.
https://chat.melkepakken.tv/v2/?twitch=false&youtube=CreatorTV&youtube_video=VIDEO_IDhttps://chat.melkepakken.tv/v2/?twitch=false&kick_room=3180237Preview mode can be used to test the overlay without relying on live chat messages.
https://chat.melkepakken.tv/v2/?preview=true&twitch=true&kick=true&youtube=true&size=3&font=0&shadow=2&animate=truePreview uses simulated messages from only the selected platforms. It needs no channel names, opens no live chat connections, and performs no YouTube discovery. Add GIFs=true to preview a native Twitch GIF promptly.
https://chat.melkepakken.tv/v2/?channel=melkepakken&kick=true&size=3&font=0&shadow=2&animate=true&emoji=twemojiWhen testing new deployments in OBS, add a cache-busting value:
https://chat.melkepakken.tv/v2/?channel=melkepakken&kick=true&size=3&font=0&shadow=2&animate=true&v=1Increase v=1 to v=2, v=3, etc. after deploying changes if OBS keeps showing an old version.
| Parameter | Example | Description |
|---|---|---|
channel |
channel=melkepakken |
Twitch channel |
twitch |
twitch=false |
Disable Twitch, even when channel is supplied |
kick |
kick=true |
Resolve Kick channel using the same name as channel |
kick |
kick=velcuz |
Resolve a specific Kick channel |
kick_channel |
kick_channel=velcuz |
Alternative Kick channel parameter |
kick_room |
kick_room=3180237 |
Manual Kick chatroom ID override |
youtube |
youtube=true |
Use the specific Kick channel when available, otherwise the Twitch channel |
youtube |
youtube=handle |
Resolve the current public live broadcast for a YouTube handle |
youtube |
youtube=https://www.youtube.com/@handle |
Resolve a YouTube channel URL |
youtube_video |
youtube_video=VIDEO_ID |
Connect directly to a live video, including an unlisted stream |
youtube_video |
youtube_video=https://youtu.be/VIDEO_ID |
Connect directly using a YouTube watch, live, or youtu.be URL |
youtube |
youtube=false |
Explicitly disable YouTube |
preview |
preview=true |
Enable preview mode |
youtube_video takes initial priority. If youtube is also present, an explicit ended-chat response for the direct stream returns to public handle discovery.
| Parameter | Example | Description |
|---|---|---|
size |
size=4 |
Large (38px); size=3 remains Extra Large (48px) |
font |
font=0 |
Font selection |
stroke |
stroke=2 |
Text stroke level |
shadow |
shadow=4 |
Layered Glow text shadow |
message_box |
message_box=true |
Add a fitted background to each message |
animate |
animate=true |
Enable smooth message animation |
fade |
fade=30 |
Fade messages after 30 seconds |
small_caps |
small_caps=true |
Use small-caps styling |
cN |
cN=%23ffcc00 |
Force all usernames to one color |
emoji |
emoji=twemoji |
Use pinned Twemoji |
emoji |
emoji=native |
Use native OS/browser emoji |
| Parameter | Example | Description |
|---|---|---|
bots |
bots=true |
Show bot messages |
hide_commands |
hide_commands=true |
Hide command messages |
hide_badges |
hide_badges=true |
Hide special/user badges, while keeping platform badges |
hide_all_badges |
hide_all_badges=true |
Hide all badges |
block |
block=user1,user2 |
Block specific usernames |
Emote size changes emotes and cheer images independently of text size. Choose Small, Medium, Large, or Extra Large. Medium keeps the original appearance, including for existing overlay links. Badges, ordinary emoji, and GIF sizes stay unchanged. The setting is included in your overlay link and shown in the live preview.
| Parameter | Example | Description |
|---|---|---|
seventv_paints |
seventv_paints=true |
Enable 7TV name paints |
GIFs |
GIFs=true |
Display native Twitch chat GIFs (off by default) |
gif_size |
gif_size=small, gif_size=medium, gif_size=large |
Twitch GIF size; Medium is the original size and default. Values ignore case and surrounding whitespace; invalid or empty values use Medium. |
emote_size |
emote_size=large |
Emote size: small, medium (default), large, or extra-large |
emote_shadow |
emote_shadow=true |
Add shadows to emotes, rendered emoji, cheers, and Twitch GIFs |
platform_badges |
platform_badges=true |
Show Twitch, Kick, and YouTube platform badges (default when omitted) |
platform_badges |
platform_badges=false |
Hide platform badges only; other badges are unchanged |
platform_badges |
platform_badges=only |
Show platform logos while hiding all other badges |
ffz_room_badges |
ffz_room_badges=true |
Enable legacy FFZ room badge lookup |
ffz_user_badges |
ffz_user_badges=true |
Enable FFZ user badges |
hide_badges=true keeps its legacy behavior: it hides special/user badges while leaving normal Twitch/Kick badges and enabled platform badges visible. hide_all_badges overrides every badge setting, so no badges are shown even with platform_badges=only.
GIFs require Twitch and the exact value true, ignoring case. The parameter name also accepts gifs and GIFS. Missing, empty, 1, yes, on, false, or whitespace-padded values leave GIFs off. With duplicate GIF parameters, every value must be a valid true. Pasted links are ordinary text. This setting is independent of animate and does not disable animated emotes.
Normal users can use the hosted setup page. The instructions below are for running your own copy.
For Twitch-only use with local Twitch credentials, run a simple static server:
python -m http.server 3000Then open:
http://localhost:3000/v2/?channel=yourtwitchchannelFor OBS, add that URL as a Browser Source.
Kick and YouTube use Cloudflare Pages Functions. Normal YouTube discovery and chat reading require no secrets. To enable the YouTube Data API fallback, create a .dev.vars file:
YOUTUBE_API_KEY=your_key_hereRun the local Pages runtime:
npx wrangler pages dev .Then open:
http://localhost:8788/v2/?twitch=false&kick=yourkickchannelWrangler provides the local Cloudflare Pages runtime. No project dependencies or build step are needed. The frontend uses plain static files. A plain static server cannot provide the Kick or YouTube helpers. To include YouTube, add &youtube=YourYouTubeHandle.
Kick connection diagnostics appear in the browser console with the jChat Kick: prefix. A missing-helper error means the Pages Function is not running. A confirmed subscription is logged only after the realtime server acknowledges it; transient failures retry with fresh connection discovery. Manual kick_room URLs also require the helper.
The hosted version at chat.melkepakken.tv uses a Cloudflare proxy for Twitch Helix requests. For local or self-hosted use, you can use a local credentials file.
Kick-only and YouTube-only overlays do not need Twitch credentials.
Copy:
v2/credentials[example].jsto:
v2/credentials.jsUse this format:
var client_id = "YOUR_TWITCH_CLIENT_ID";
var oauth_token = "YOUR_TWITCH_APP_ACCESS_TOKEN";The variable names are intentionally lowercase because the current jChat+ Twitch helper expects:
client_id
oauth_tokenCreate a Twitch application in the Twitch Developer Console, then use your Client ID and Client Secret to generate an app access token.
Example:
curl -X POST "https://id.twitch.tv/oauth2/token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=client_credentials"The response contains an access_token. Use that value as oauth_token.
In the existing Pages project's Settings > Build & deployments, use:
| Setting | Value |
|---|---|
| Framework preset | None |
| Root directory | Repository root (leave blank) |
| Build command | exit 0 |
| Build output directory | / (repository root) |
Pages compiles the standard functions/ directory automatically. There is no dependency installation, custom build command, or generated _worker.js to maintain. Only API routes invoke Functions; ordinary static assets stay on Pages' static asset service. Keep the existing secrets and compatibility settings; no new bindings are needed. Local compilation does not replace checking the next approved Git deployment.
The public deployment uses:
Cloudflare Pages
chat.melkepakken.tv
/functions/api/twitch/[[path]].js
/functions/api/kick/realtime.js
/functions/api/kick/socket.js
/functions/api/youtube/live.js
/functions/api/youtube/chat.jsThe Cloudflare Function proxies the Twitch Helix endpoints used by jChat+:
/api/twitch/users
/api/twitch/chat/badges/global
/api/twitch/chat/badges
/api/twitch/bits/cheermotesThe YouTube Functions handle separate parts of YouTube support:
functions/api/youtube/live.jschecks the channel's public live listings first. Confirmed offline channels, including channels with only scheduled broadcasts, return immediately without Data API calls. Only failed or inconclusive lookups use the existing official YouTube Data API v3 fallback.functions/api/youtube/chat.jsuses the public web chat connector to read Live Chat messages, custom emoji, and deletion actions.
jChat+ includes its client version on YouTube backend requests for diagnostics only; it is not used for authentication or runtime behavior.
Discovery backoff and quota cooldowns use Cloudflare's Cache API. Its contents are local to the data center handling the request and may be evicted; concurrent work is coalesced only within the current Worker isolate. These safeguards reduce duplicate API calls, but they are not global coordination or an unlimited-scale guarantee.
Live Chat reading and public-listing discovery do not require YouTube OAuth, an API key, or external libraries. The Data API fallback remains available when public listings cannot confirm a result.
Cloudflare environment variables/secrets for Twitch and the optional YouTube Data API fallback:
TWITCH_CLIENT_ID
TWITCH_CLIENT_SECRET
YOUTUBE_API_KEYTWITCH_CLIENT_SECRET and YOUTUBE_API_KEY must be saved as Cloudflare secrets.
For the YouTube fallback, enable YouTube Data API v3 in Google Cloud, create an API key, and restrict it to YouTube Data API v3. Save that key as YOUTUBE_API_KEY in Cloudflare. Existing installations can keep their current key.
The frontend should never expose either value.
The v2 overlay is split into plain browser scripts loaded directly from v2/index.html.
There is no bundler, no ES modules, and no imports. Load order matters because each file extends the global Chat object.
The scripts are separated by responsibility: shared state and styles, preview data, emotes and badges, platform connections, message rendering, and startup. Because they share the global Chat object, changes to script order or shared helpers should be checked carefully.
jChat+ is developed with the assistance of AI coding tools.
I use AI to help navigate API documentation, inspect code, debug issues, review changes, and explore implementation approaches.
Project direction, architecture, feature decisions, testing, and releases are handled by me. Changes are reviewed and tested before they are merged or released.
Never commit or publish credentials. In particular, do not commit:
v2/credentials.js
.dev.vars
.dev.vars.*
.env
.env.*Treat the oauth_token in v2/credentials.js as a credential, and never commit or publish your Twitch Client Secret. The frontend must not expose TWITCH_CLIENT_SECRET or YOUTUBE_API_KEY.
Hosted deployments should store TWITCH_CLIENT_SECRET and YOUTUBE_API_KEY as Cloudflare secrets instead of frontend credentials.
Forks of this project do not receive the original Cloudflare secrets. Anyone deploying their own version must provide their own Twitch app credentials to use Twitch, and their own YouTube Data API v3 key to enable the YouTube discovery fallback.
jChat+ is a fork of the original jChat by giambaJ.
YouTube deterministic username colours are based on the username colour hashing from BetterYTL by datagutt (Thomas Lekanger).
jChat+ is not affiliated with Twitch, Kick, or YouTube.
This project follows the license of the original jChat project.
