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.
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 backs off while a channel remains offline, from about one minute to several minutes between checks. A new public broadcast may therefore take a few minutes to appear, and can take longer when YouTube discovery or search is unavailable or quota-limited. This does not slow polling after Live Chat is connected. A direct-only youtube_video configuration bypasses Data API discovery. With a fallback youtube handle, repeated generic direct-chat failures may trigger a quota-safe check for a different current public live stream; an offline handle does not abandon the direct video. If a daily discovery quota is exhausted, affected calls pause until the next Pacific-time reset and retry automatically.
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 |
| 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_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.
Twitch and Kick can be served as a static browser-based overlay. 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.
YouTube uses Cloudflare Pages Functions. For local Wrangler use, create a .dev.vars file:
YOUTUBE_API_KEY=your_key_hereThen run the local Pages runtime:
npx wrangler pages dev .Then open:
http://localhost:8788/v2/?channel=yourtwitchchannel&youtube=trueThis does not introduce a build process. Wrangler is only the local Cloudflare Pages runtime. The Python static server remains enough for Twitch and Kick.
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.
The public deployment uses:
Cloudflare Pages
chat.melkepakken.tv
/functions/api/twitch/[[path]].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.jsuses the official YouTube Data API v3 to find the current live stream for a handle.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 does not require YouTube OAuth.
Required Cloudflare environment variables/secrets:
TWITCH_CLIENT_ID
TWITCH_CLIENT_SECRET
YOUTUBE_API_KEYTWITCH_CLIENT_SECRET and YOUTUBE_API_KEY must be saved as Cloudflare secrets.
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.
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 and YouTube Data API v3 key.
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.
