| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
telegram-webapp-sdk provides a type-safe and ergonomic wrapper around the Telegram Web Apps JavaScript API.
Note
Comprehensive Coverage
This project achieves comprehensive test coverage for both native and WASM code:
Coverage reports include all modules (leptos, yew, api, webapp, logger, pages, router) ensuring quality across the entire codebase.
For implementation details, see issue #130.
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
The macros are available with the macros feature. Enable it in your Cargo.toml:
telegram-webapp-sdk = { version = "0.11", features = ["macros"] }Reduce boilerplate in Telegram Mini Apps using the provided macros:
telegram_page!("/", fn index() {
// render page
});
telegram_app!(fn main() -> Result<(), wasm_bindgen::JsValue> {
telegram_router!();
Ok(())
});When running outside Telegram in debug builds, telegram_app! loads mock settings from telegram-webapp.toml.
The macros feature ships with a minimal in-memory Router that collects pages registered via telegram_page!. The telegram_router! macro builds this router and runs all page handlers:
telegram_page!("/", pub fn index() {});
// Uses the default Router
telegram_router!();Provide a custom router type to the macro if additional behavior is required:
struct CustomRouter;
impl CustomRouter {
fn new() -> Self { CustomRouter }
fn register(self, _path: &str, _handler: fn()) -> Self { self }
fn start(self) {}
}
telegram_router!(CustomRouter);Add the crate to your Cargo.toml:
[dependencies]
telegram-webapp-sdk = "0.11"Enable optional features as needed:
telegram-webapp-sdk = { version = "0.11", features = ["macros", "yew", "leptos", "mock"] }Use the SDK directly with pure WebAssembly - no framework required:
use telegram_webapp_sdk::{
core::init::init_sdk,
webapp::TelegramWebApp,
dom::{Document, ElementExt},
};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn main() -> Result<(), JsValue> {
init_sdk()?;
TelegramWebApp::instance()
.ok_or_else(|| JsValue::from_str("Telegram not available"))?
.ready()?;
let doc = Document;
let root = doc.create_element("div")?;
root.set_class("container");
let btn = doc.create_element("button")?;
btn.set_text("Click me");
btn.set_class("btn-primary");
btn.on("click", |_| {
web_sys::console::log_1(&"Clicked!".into());
})?;
root.append(&btn)?;
doc.body()?.append(&root)?;
Ok(())
}The SDK includes ergonomic DOM manipulation helpers:
use telegram_webapp_sdk::dom::{Document, ElementExt};
// Get element by ID or selector
let el = Document.get_element_by_id("my-id");
let first = Document.query_selector(".item")?;
// Element manipulation
element.set_class("active");
element.set_id("unique-id");
element.set_text("Hello!");
element.set_html("<strong>Bold</strong>")?;
element.set_attr("data-value", "123")?;
element.remove_attr("data-value")?;
// Class manipulation
element.add_class("highlighted")?;
element.remove_class("hidden")?;
element.toggle_class("expanded")?;
let is_active = element.has_class("active");
// Event handling
element.on("click", |event| { /* handle click */ })?;
element.on("input", |event| { /* handle input */ })?;
// Tree manipulation
element.append(&child)?;
element.prepend(&header)?;
element.remove()?; // detach self from parent
element.clear(); // remove all childrenSee examples/vanilla for a complete working example.
use telegram_webapp_sdk::yew::use_telegram_context;
use yew::prelude::*;
#[function_component(App)]
fn app() -> Html {
let ctx = use_telegram_context().expect("context");
if let Some(query_id) = ctx.init_data.query_id.as_deref() {
// Handle inline query response with `answerWebAppQuery`.
let _ = query_id;
}
html! { <span>{ ctx.init_data.auth_date }</span> }
}Yew also ships components for all three system buttons:
use telegram_webapp_sdk::yew::{BackButton, BottomButton, SettingsButton};
use yew::prelude::*;
#[function_component(App)]
fn app() -> Html {
let on_main = Callback::from(|_| {});
let on_back = Callback::from(|_| {});
let on_settings = Callback::from(|_| {});
html! {
<>
<BottomButton text="Send" color="#000" text_color="#fff" on_click={on_main} />
<BackButton visible={true} on_click={on_back} />
<SettingsButton visible={true} on_click={on_settings} />
</>
}
}use leptos::prelude::*;
use telegram_webapp_sdk::leptos::provide_telegram_context;
#[component]
fn App() -> impl IntoView {
provide_telegram_context().expect("context");
let ctx = use_context::<telegram_webapp_sdk::core::context::TelegramContext>()
.expect("context");
if let Some(query_id) = ctx.init_data.query_id.as_deref() {
// Handle inline query response with `answerWebAppQuery`.
let _ = query_id;
}
view! { <span>{ ctx.init_data.auth_date }</span> }
}The SDK also provides BottomButton, BackButton, and SettingsButton components for Leptos that drive the corresponding native Telegram buttons:
use leptos::prelude::*;
use telegram_webapp_sdk::leptos::{
provide_telegram_context, BackButton, BottomButton, SettingsButton
};
use telegram_webapp_sdk::webapp::BottomButton as Btn;
#[component]
fn App() -> impl IntoView {
provide_telegram_context().expect("context");
let (text, _set_text) = signal("Send".to_owned());
let back_visible = RwSignal::new(true);
view! {
<BottomButton button=Btn::Main text />
<BackButton visible=back_visible on_click=move || { /* navigate back */ } />
<SettingsButton visible=back_visible on_click=move || { /* open settings */ } />
}
}Every one-shot Telegram callback has an async fn sibling that returns the natural Rust type. Prefer .await for prod code; use the *_with_callback variant when you can't .await (e.g. inside a non-async closure):
use telegram_webapp_sdk::webapp::TelegramWebApp;
# async fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
let confirmed: bool = app.show_confirm("Send the order?").await?;
let scanned: String = app.show_scan_qr_popup("Scan a QR code").await?;
let granted: bool = app.request_write_access().await?;
let _ = (confirmed, scanned, granted);
# Ok(())
# }The same applies to share_message, request_chat, check_home_screen_status, set_emoji_status, request_emoji_status_access, open_invoice, download_file, read_text_from_clipboard, show_popup, and invoke_custom_method.
Both Yew and Leptos integrations ship reactive hooks over Telegram's state-changing events. The signals are seeded with the current values and re-render the component when Telegram fires viewportChanged, themeChanged, safeAreaChanged, or contentSafeAreaChanged. Cleanup is automatic on unmount / scope disposal.
// Leptos
use leptos::prelude::*;
use telegram_webapp_sdk::leptos::{use_safe_area, use_theme, use_viewport};
#[component]
fn Status() -> impl IntoView {
let viewport = use_viewport();
let theme = use_theme();
let safe = use_safe_area();
view! {
<div>
{ move || viewport.get().height }
{ move || theme.get().color_scheme.unwrap_or_default() }
{ move || safe.get().area.map(|i| i.top).unwrap_or(0.0) }
</div>
}
}// Yew
use telegram_webapp_sdk::yew::{use_safe_area, use_theme, use_viewport};
use yew::prelude::*;
#[function_component(Status)]
fn status() -> Html {
let viewport = use_viewport();
let theme = use_theme();
let safe = use_safe_area();
html! {
<div>
{ viewport.height }
{ theme.color_scheme.clone().unwrap_or_default() }
{ safe.area.map(|i| i.top).unwrap_or(0.0) }
</div>
}
}The mock feature simulates a Telegram.WebApp instance, enabling local development without Telegram:
use telegram_webapp_sdk::mock::{config::MockTelegramConfig, init::mock_telegram_webapp};
let config = MockTelegramConfig::default();
mock_telegram_webapp(config)?;Request access to sensitive user data with the async API (preferred):
use telegram_webapp_sdk::api::user::request_contact;
use telegram_webapp_sdk::webapp::TelegramWebApp;
# async fn run() -> Result<(), wasm_bindgen::JsValue> {
request_contact()?;
let app = TelegramWebApp::try_instance()?;
let granted: bool = app.request_write_access().await?;
let sent: bool = app.request_chat(42).await?;
let _ = (granted, sent);
# Ok(())
# }A synchronous callback variant is available as *_with_callback for code that can't .await (e.g. app.request_write_access_with_callback(|granted| { … })). All calls require the user's explicit permission before any information is shared.
Control the native keyboard and bottom buttons (Main and Secondary):
use telegram_webapp_sdk::webapp::{BottomButton, BottomButtonParams, TelegramWebApp};
# fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
// Hide the native keyboard
app.hide_keyboard()?;
// Control the main bottom button
app.set_main_button_text("Send")?;
app.set_main_button_color("#2481cc")?;
app.set_main_button_text_color("#ffffff")?;
app.enable_main_button()?;
app.show_main_button()?;
// Set custom emoji icon on the button (Bot API 9.5+)
app.set_main_button_icon_custom_emoji_id("123456789")?;
// Or use setParams for atomic updates
let params = BottomButtonParams {
text: Some("Submit"),
color: Some("#ff0000"),
text_color: Some("#ffffff"),
is_active: Some(true),
is_visible: Some(true),
icon_custom_emoji_id: Some("987654321"), // Bot API 9.5+
..Default::default()
};
app.set_main_button_params(¶ms)?;
// Secondary button (also supports icon_custom_emoji_id)
app.set_secondary_button_text("Cancel")?;
app.set_secondary_button_icon_custom_emoji_id("111222333")?;
app.show_secondary_button()?;
# Ok(())
# }Prompt users before the Mini App closes:
use telegram_webapp_sdk::webapp::TelegramWebApp;
# fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
app.enable_closing_confirmation()?;
assert!(app.is_closing_confirmation_enabled());
// later
app.disable_closing_confirmation()?;
# Ok(())
# }Open invoices and react to the final payment status:
use telegram_webapp_sdk::webapp::TelegramWebApp;
# async fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
let handle = app.on_invoice_closed(|status| {
let _ = status;
})?;
let status: String = app.open_invoice("https://invoice").await?;
let _ = status;
app.off_event(handle)?;
# Ok(())
# }Share links, prepared messages, or stories and join voice chats:
use js_sys::Object;
use telegram_webapp_sdk::webapp::TelegramWebApp;
# async fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
app.share_url("https://example.com", Some("Check this out"))?;
let sent: bool = app.share_message("msg-id").await?;
let _ = sent;
let params = Object::new();
app.share_to_story("https://example.com/image.png", Some(¶ms.into()))?;
# Ok(())
# }Control the Telegram client's settings button and handle user clicks through the unified TelegramWebApp API:
use telegram_webapp_sdk::webapp::TelegramWebApp;
# fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
app.show_settings_button()?;
let handle = app.set_settings_button_callback(|| {
// user opened the settings menu
})?;
// when no longer needed:
app.remove_settings_button_callback(handle)?;
app.hide_settings_button()?;
# Ok(())
# }The legacy standalone helpers in api::settings_button (show, hide, on_click, off_click) remain available for callers that prefer the free function style.
Persist small key-value pairs in Telegram's cloud using CloudStorage:
use js_sys::Reflect;
use telegram_webapp_sdk::api::cloud_storage::{get_items, set_item};
use wasm_bindgen_futures::JsFuture;
# async fn run() -> Result<(), wasm_bindgen::JsValue> {
JsFuture::from(set_item("counter", "1")?).await?;
let obj = JsFuture::from(get_items(&["counter"])?).await?;
let value = Reflect::get(&obj, &"counter".into())?.as_string();
assert_eq!(value, Some("1".into()));
# Ok(())
# }All functions return a Promise and require the Web App to run inside Telegram.
## Home screenPrompt users to add the app to their home screen and check the current status:
use telegram_webapp_sdk::webapp::TelegramWebApp;
# async fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
let _shown = app.add_to_home_screen()?;
let status: String = app.check_home_screen_status().await?;
let _ = status;
# Ok(())
# }Callback registration methods return an EventHandle for later deregistration.
use telegram_webapp_sdk::webapp::TelegramWebApp;
# fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
let handle = app.on_event("my_event", |value| {
let _ = value;
})?;
app.off_event(handle)?;
# Ok(())
# }Some Telegram events may fire while the Mini App is in the background. Register callbacks for these with on_background_event:
use telegram_webapp_sdk::webapp::{BackgroundEvent, TelegramWebApp};
# fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
let handle = app.on_background_event(BackgroundEvent::MainButtonClicked, |_| {})?;
app.off_event(handle)?;
# Ok(())
# }Supported background events:
| Event | Payload |
|---|---|
| mainButtonClicked | none |
| backButtonClicked | none |
| settingsButtonClicked | none |
| writeAccessRequested | bool granted flag |
| contactRequested | bool shared flag |
| invoiceClosed | status String |
| popupClosed | object { button_id: Option<String> } |
| qrTextReceived | scanned text String |
| clipboardTextReceived | clipboard text String |
| requestedChatSent | none (Bot API 9.6) |
| requestedChatFailed | object { error: String } (Bot API 9.6) |
Customize colors and react to theme or safe area updates:
use telegram_webapp_sdk::api::theme::get_theme_params;
use telegram_webapp_sdk::webapp::TelegramWebApp;
# fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
app.set_header_color("#0a0a0a")?;
app.set_background_color("#ffffff")?;
app.set_bottom_bar_color("#2481cc")?;
let params = get_theme_params()?;
let _ = params.bg_color;
let theme_handle = app.on_theme_changed(|| {
let _ = get_theme_params();
})?;
let safe_handle = app.on_safe_area_changed(|| {})?;
let content_handle = app.on_content_safe_area_changed(|| {})?;
app.off_event(theme_handle)?;
app.off_event(safe_handle)?;
app.off_event(content_handle)?;
# Ok(())
# }Inspect the Mini App viewport size and subscribe to updates:
use telegram_webapp_sdk::api::viewport::{
expand_viewport, get_viewport_height, on_viewport_changed,
};
use wasm_bindgen::closure::Closure;
# fn run() -> Result<(), wasm_bindgen::JsValue> {
let _ = get_viewport_height();
let callback = Closure::wrap(Box::new(|| {
let _ = get_viewport_height();
}) as Box<dyn Fn()>);
on_viewport_changed(&callback);
expand_viewport()?;
callback.forget();
# Ok(())
# }Control the Mini App display and screen orientation:
use telegram_webapp_sdk::webapp::TelegramWebApp;
# fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
if !app.is_fullscreen() {
app.request_fullscreen()?;
}
app.lock_orientation("portrait")?;
app.unlock_orientation()?;
app.exit_fullscreen()?;
# Ok(())
# }Trigger device vibrations through Telegram's HapticFeedback API:
use telegram_webapp_sdk::api::haptic::{
impact_occurred, notification_occurred, selection_changed,
HapticImpactStyle, HapticNotificationType,
};
impact_occurred(HapticImpactStyle::Light)?;
notification_occurred(HapticNotificationType::Success)?;
selection_changed()?;
# Ok::<(), wasm_bindgen::JsValue>(())Persist lightweight data on the user's device:
use telegram_webapp_sdk::api::device_storage::{set, get};
# async fn run() -> Result<(), wasm_bindgen::JsValue> {
set("theme", "dark").await?;
let value = get("theme").await?;
# Ok(())
# }Store sensitive data encrypted and restorable:
use telegram_webapp_sdk::api::secure_storage::{set, restore};
# async fn run() -> Result<(), wasm_bindgen::JsValue> {
set("token", "secret").await?;
let _ = restore("token").await?;
# Ok(())
# }Guard privileged actions behind the BiometricManager API:
use telegram_webapp_sdk::api::biometric::{
authenticate, init, is_biometric_available, request_access,
};
# fn run() -> Result<(), wasm_bindgen::JsValue> {
init()?;
if is_biometric_available()? {
request_access("auth-key", Some("Unlock the vault"), None)?;
authenticate("auth-key", None, None)?;
}
# Ok(())
# }Retrieve user location and react to related events via Telegram's location manager:
use telegram_webapp_sdk::api::location_manager::{
init, get_location, open_settings, on_location_requested,
};
use wasm_bindgen::closure::Closure;
init()?;
let _ = get_location();
open_settings()?;
let cb = Closure::wrap(Box::new(|| {}) as Box<dyn Fn()>);
on_location_requested(&cb)?;
cb.forget();
# Ok::<(), wasm_bindgen::JsValue>(())Access motion sensors if the user's device exposes them.
use telegram_webapp_sdk::api::accelerometer::{start, get_acceleration, stop};
start()?;
let reading = get_acceleration();
stop()?;
# Ok::<(), wasm_bindgen::JsValue>(())Callbacks for sensor lifecycle events are available through on_started, on_changed, on_stopped, and on_failed functions for accelerometer, gyroscope, and device orientation sensors.
Retrieve the raw URL-encoded initData string for server-side authentication. The SDK captures this string during initialization and provides convenient access without requiring JavaScript reflection:
use telegram_webapp_sdk::TelegramWebApp;
# fn run() -> Result<(), Box<dyn std::error::Error>> {
// Get raw initData for backend validation
let raw_init_data = TelegramWebApp::get_raw_init_data()?;
// Send to your backend for signature verification
// POST /auth with body: { "init_data": raw_init_data }
# Ok(())
# }This eliminates the need for manual Reflect calls and ensures consistency with the parsed data available in the context.
Server-side validation is required. Use the init-data-rs crate for backend validation:
// On your backend server
use init_data_rs::{validate, InitData};
async fn authenticate(init_data_str: &str, bot_token: &str) -> Result<InitData, Box<dyn std::error::Error>> {
// Validate with optional expiration time (in seconds)
let init_data: InitData = validate(init_data_str, bot_token, Some(3600))?;
Ok(init_data)
}Why server-side only?
See the init-data-rs documentation for complete usage examples.
WebApp API coverage: version 9.6 matches the latest Telegram WebApp API release 9.6. Synced in commit 53276fd (recorded on 2026-05-13).
See WEBAPP_API.md for a checklist of supported Telegram WebApp JavaScript API methods and features.
See CHANGELOG.md for release notes.
telegram-webapp-sdk is licensed under the MIT license — see LICENSES/MIT.txt or http://opensource.org/licenses/MIT.
| Back | FazBrowse Home | New Git URL |