tg-mini-app
1.2.0indexedEnables creation of Telegram mini apps, offering features like viewport management, theme adaptation, and seamless integration with Telegram WebApp API for enhanced user experience.
Enables creation of Telegram mini apps, offering features like viewport management, theme adaptation, and seamless integration with Telegram WebApp API for enhanced user experience.
Kotlin Multiplatform library for building Telegram Mini Apps with Compose Multiplatform for the web (js and wasmJs).
It provides:
telegramWebApp { ... } entry point for Compose UIwindow.Telegram.WebApp2.4.201.12.1js and wasmJs (browser)10.1, every member is marked with the Bot API version it needsTelegram clients may support an older Bot API version than the one your code uses. Check the runtime version with webApp.isVersionAtLeast(...) before using newer features.
js and/or wasmJs targetwebMain (or jsMain / wasmJsMain)Add the Telegram runtime script to your page:
<script src="https://telegram.org/js/telegram-web-app.js"></script>
Add the library dependency:
dependencies {
implementation("io.github.kirillNay:tg-mini-app:2.0.0")
}
Use telegramWebApp as the entry point of your Telegram-hosted web app:
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import com.kirillNay.telegram.miniapp.compose.telegramWebApp
import com.kirillNay.telegram.miniapp.webApp.webApp
fun main() {
telegramWebApp { style ->
LaunchedEffect(Unit) {
webApp.ready()
webApp.expand()
}
Surface(
color = style.colors.backgroundColor,
) {
Text("Hello, ${webApp.initDataUnsafe.user?.firstName ?: "Telegram"}")
}
}
}
telegramWebApp gives your content a TelegramStyle instance, also available as LocalTelegramStyle:
style.colors - current Telegram theme colors mapped to Compose ColorThese values are refreshed when Telegram emits the corresponding WebApp events.
If the page can also be opened in a regular browser, pass a fallback. It is shown instead of the Mini App when the page was not launched by Telegram:
telegramWebApp(fallback = { OpenInTelegramScreen() }) { style ->
App(style)
}
The library adds only a thin Compose integration layer:
telegramWebApp { style ->
App(
background = style.colors.backgroundColor,
primary = style.colors.buttonColor,
viewportHeight = style.viewPort.viewportStableHeight,
)
}
This makes it easy to keep Telegram-specific code near your web (webMain) entry point while the rest of your UI stays platform-agnostic.
Use the global webApp instance to access Telegram Mini App features in Kotlin style.
import com.kirillNay.telegram.miniapp.webApp.webApp
webApp.ready()
webApp.expand()
webApp.enableClosingConfirmation()
webApp.setBackgroundColor("bg_color")
webApp.setHeaderColor("secondary_bg_color")
Accessing webApp throws if telegram-web-app.js is not loaded. Use WebApp.isAvailable, WebApp.isRunningInTelegram or WebApp.getOrNull() to check first.
Methods with a callback have await* suspend counterparts:
webApp.showConfirm("Exit checkout?") { isConfirmed ->
if (isConfirmed) webApp.close()
}
suspend fun confirmExit() {
if (webApp.awaitConfirm("Exit checkout?")) webApp.close()
}
val subscription = webApp.onEvent(WebAppEvent.ViewportChanged) { isStateStable ->
if (isStateStable) saveLayout()
}
subscription.unsubscribe()
webApp.backButton.onClick { navigateBack() }
webApp.backButton.show()
webApp.mainButton
.setText("Pay")
.onClick { submitOrder() }
.show()
// Bot API 7.10+
webApp.secondaryButton
.setParams(BottomButtonParams(text = "Cancel", position = BottomButtonPosition.LEFT, isVisible = true))
offClick accepts the same lambda instance that was passed to onClick.
cloudStorage (Bot API 6.9+), deviceStorage and secureStorage (Bot API 9.0+) are suspend APIs returning Result. Telegram errors are reported as WebAppException.
suspend fun saveDraft(note: String) {
webApp.cloudStorage.setItem("draft_note", note)
}
suspend fun loadDraft(): String {
return webApp.cloudStorage.getItem("draft_note").getOrDefault("")
}
webApp.hapticFeedback.impactOccurred(HapticFeedback.ImpactStyle.LIGHT)
webApp.hapticFeedback.notificationOccurred(HapticFeedback.NotificationType.SUCCESS)
The repository includes a complete sample in samples/coffee-order-demo:
commonMainwebMain (built for js and wasmJs)androidApp module) and iOS demo hosts reusing the same Compose UIYou can also try the Telegram demo bot at @tgminiapp_demo_bot or open it directly: t.me/tgminiapp_demo_bot/demo.
Run the sample from the repository root:
./gradlew -p samples/coffee-order-demo :composeApp:wasmJsBrowserDevelopmentRun
./gradlew -p samples/coffee-order-demo :composeApp:jsBrowserDevelopmentRun
./gradlew -p samples/coffee-order-demo :androidApp:assembleDebug
./gradlew -p samples/coffee-order-demo :composeApp:linkDebugFrameworkIosSimulatorArm64
More details are available in samples/coffee-order-demo/README.md.
The wrapper covers the Telegram WebApp API up to Bot API 10.1:
signature and chat_join_request_query_id) and runtime metadataThe API is designed to stay close to the official Telegram Mini Apps documentation, but exposed with Kotlin naming and suspend-friendly wrappers where it improves ergonomics.
2.0 changes the public API to be type-safe and to work on both js and wasmJs:
Publishing moved from the Sonatype staging plugin to com.vanniktech.maven.publish. CI passes the existing SONATYPE_* and SIGNING_* secrets as ORG_GRADLE_PROJECT_mavenCentral* and ORG_GRADLE_PROJECT_signingInMemory* properties.
See CHANGELOG.md.
style.colorScheme / style.isDark - light or dark Telegram themestyle.viewPort.height - current visible Mini App heightstyle.viewPort.stableHeight - stable viewport height for bottom-pinned UIstyle.safeAreaInset / style.contentSafeAreaInset - Bot API 8.0+ insets as PaddingValuesjs and wasmJs). It is not a general-purpose browser wrapper.window.Telegram.WebApp must be available before you use telegramWebApp or webApp.fallback to telegramWebApp or check WebApp.isRunningInTelegram.js target the library waits for the Skiko runtime before rendering; for wasmJs it renders right away. To serve both, build the sample-style composeCompatibilityBrowserDistribution, which picks Wasm and falls back to JS in browsers without WasmGC.initDataUnsafe as untrusted client data. Validate rawInitData on your server as described in the Telegram docs.| 1.x | 2.0 |
|---|
webApp.addEventHandler(EventType.X) { any -> } / removeEventHandler | webApp.onEvent(WebAppEvent.X) { payload -> } returns an EventSubscription |
webApp.enableClosingConfirmation(Boolean) | enableClosingConfirmation() / disableClosingConfirmation() |
webApp.mainButton: MainButton | webApp.mainButton: BottomButton, plus secondaryButton |
ButtonParams(textColor = ..., isActive = ...) | BottomButtonParams(...) |
hapticFeedback.impactOccurred("light") | impactOccurred(HapticFeedback.ImpactStyle.LIGHT) |
BackButton.show() returns Unit | returns BackButton for chaining |
ColorScheme.getValue(...) / InvoiceStatus.getValue(...) throw on unknown values | unknown values map to a fallback (LIGHT, InvoiceStatus.UNKNOWN) |
ChatType.CHANNEL | ChatType.CHANNELS |
switchInlineQuery(query, vararg chatType) | switchInlineQuery(query, chatTypes: List<ChatType>) |
openLink(url, tryInstantView: Boolean?) | openLink(url, tryInstantView: Boolean = false) |
PopupParams(title, message, buttons: Array) / PopupButton(id, text, buttonType) | PopupParams(message, title, buttons: List) / PopupButton(id, type, text) |
readTextFromClipboard() suspend overload | awaitClipboardText(); other popups also have await* variants |
webApp.viewportHeight: Float | Double |
WebAppUser, WebAppChat, WebAppInitData, ThemeParams external classes | Kotlin data classes; WebAppChat.userName is now username, authDate is Long? |
ViewPort.viewPortHeight / viewportStableHeight | ViewPort.height / stableHeight |
TelegramColors.fromWebApp() | TelegramColors.from(webApp.themeParams) |
CloudStorage.removeItems(vararg) etc. | same, plus List overloads; errors are WebAppException |
Surfaced from shared tags and platforms — no rankings paid for.