Introduction
Support Frix is a framework-agnostic support widget. You embed a single custom element, and your users get a polished button that opens a modal where they pick a category and send a message. Tickets are saved to your database and mirrored to a Telegram channel, and your replies flow back into the widget in real time.
The widget renders inside the Shadow DOM, so it is fully isolated: your CSS can't break it and its CSS can't leak into your app.
Installation
Load the bundle once, then place the element anywhere in your markup.
<script type="module"
src="https://support.frix.me/widget/support-frix-widget.js"></script>
<support-frix-widget
api-key="YOUR_API_KEY"
auth-mode="both"
></support-frix-widget>Get YOUR_API_KEY from the dashboard → Platforms. Self-hosting? Point the script at your own /widget/ path and set api-url accordingly.
Configuration
Everything is a plain HTML attribute. No bundler, no config files.
| Attribute | Default | Notes |
|---|---|---|
| api-key | — | Required. Platform key from the dashboard. |
| api-url | https://support.frix.me/api | Base URL of the API. |
| auth-mode | both | email · telegram · both |
| primary-color | #6366f1 | Accent color. |
| btn-radius | 9999px | Launcher corner radius. |
| btn-position | bottom-right | bottom-right · bottom-left |
| btn-label | Support | Launcher button text. |
| title | How can we help? | Modal heading. |
| theme | light | light · dark · auto |
Identity modes
The auth-mode attribute decides how a user identifies before sending a ticket.
email— the user leaves a name and email. Lowest friction; ideal for public marketing sites.telegram— the user enters their Telegram, receives a 6-digit code from the bot, and confirms. Replies then reach them directly on Telegram.both— show both options and let the user pick. A sensible default.
Receiving replies
However a user identified, your answer reaches them:
- Inside the widget. On submit, the widget stores a private
publicTokeninlocalStorageand polls/api/tickets/:token. When you reply, the conversation updates automatically — no login required. - On Telegram. If the user verified via Telegram, the bot also DMs them your answer with the platform name as a header.
Framework guides
Because it's a standard web component, integration is nearly identical everywhere — only the "allow this custom tag" detail differs.
// React treats unknown tags as custom elements automatically.
import { useEffect } from "react";
export function SupportButton() {
useEffect(() => {
const s = document.createElement("script");
s.type = "module";
s.src = "https://support.frix.me/widget/support-frix-widget.js";
document.body.appendChild(s);
}, []);
return (
// @ts-expect-error — custom element has no JSX types
<support-frix-widget api-key="YOUR_API_KEY" auth-mode="both" />
);
}Styling & theming
Match your brand with primary-color, btn-radius, btn-position and theme. Because the widget lives in the Shadow DOM, these attributes are the supported styling surface — your global stylesheet won't reach inside.
<support-frix-widget
api-key="YOUR_API_KEY"
primary-color="#0ea5e9"
btn-radius="14px"
btn-position="bottom-left"
btn-label="Help"
theme="dark"
></support-frix-widget>Create the bot
You only do this once for your whole installation.
- Open @BotFather in Telegram and send
/newbot. - Choose a name and a username (e.g.
SuppFrixBot). - Copy the token BotFather gives you and set it as
TELEGRAM_BOT_TOKENin your API.env.
Create the channel & add the bot
This is the channel every ticket is mirrored into. Do it from your own Telegram account — a bot can't create channels.
- In Telegram: New Channel → name it (e.g. "Frix Support"). Private is fine.
- Open the channel → Administrators → Add Admin.
- Search
@SuppFrixBotand add it. Grant at least Post Messages. - Get the channel ID: forward any channel post to @getidsbot, or temporarily make the channel public and read it from the API. Channel IDs look like
-1001234567890.
Wire it to the API
Add the channel ID to your environment and restart the API.
# apps/api/.env
TELEGRAM_BOT_TOKEN="123456:ABC-yourtoken"
TELEGRAM_BOT_USERNAME="SuppFrixBot"
TELEGRAM_CHANNEL_ID="-1001234567890"
# production webhook (https, public)
TELEGRAM_WEBHOOK_URL="https://support.frix.me/api/telegram/webhook"In development the bot uses long-polling automatically. In production it registers the webhook above so Telegram delivers owner replies to the API.
Using the dashboard
- Platforms — create one per app you embed in. Each gets its own API key and categories.
- Categories — the options users pick from when writing in.
- Tickets — a master-detail inbox. Pick a ticket, read the thread, and reply from the field at the bottom; the answer is pushed to the user.
- Telegram toggle — turn channel/DM notifications on or off per platform.
API reference
The widget only touches the public endpoints; the rest power the dashboard and bot.
| GET | /api/platforms/categories?apiKey=… | Public. Categories for a platform — used by the widget to populate the dropdown. |
| POST | /api/tickets/create | Public. Create a ticket. Returns a publicToken the widget keeps to poll for replies. |
| GET | /api/tickets/:publicToken | Public. Ticket + full message thread, by token. Powers the widget conversation view. |
| POST | /api/tickets/:publicToken/messages | Public. Append a follow-up message from the user. |
| POST | /api/auth/request-code | Send a 6-digit Telegram verification code to a telegramId. |
| POST | /api/auth/verify-code | Verify the code, mark the user verified, return a short-lived token. |
| POST | /api/auth/login | Owner login for the dashboard. Returns a JWT. |
| GET | /api/owner/tickets | Auth. List tickets across the owner's platforms, filterable by status/platform. |
| POST | /api/owner/tickets/:id/reply | Auth. Post an answer. Delivered to the user via the widget and Telegram. |
| POST | /api/telegram/webhook | Telegram → API. Handles owner replies coming from the channel/DM. |
Self-hosting
Support Frix is a small monorepo: a NestJS API, a Next.js dashboard, and the Vite widget.
# build everything
npm install
npm run build # api + dashboard + widget
# database
npm run db:up # postgres via docker
npm run db:deploy -w api
# run with pm2 (api :3000, dashboard :3001)
pm2 start ecosystem.config.jsnginx serves the built widget from /widget/, proxies /api/ to the API and everything else to the dashboard. The full nginx block ships in SETUP.md.
Ready to embed it? Grab the snippet for your framework →