Skip to content

3. Core Concepts

RAprogramm edited this page Nov 4, 2025 · 1 revision

Chapter 3: Core Concepts

This chapter explains the fundamental concepts you need to understand to build Telegram Mini Apps with telegram-webapp-sdk.

SDK Initialization

The Initialization Process

When your WebApp loads, the SDK must initialize before you can use any Telegram features. The initialization process:

1. Browser loads your HTML + WASM
           ↓
2. Telegram script creates window.Telegram.WebApp
           ↓
3. init_sdk() reads Telegram data
           ↓
4. SDK creates global TelegramContext
           ↓
5. Your app can now use Telegram APIs

Two Ways to Initialize

Automatic (Recommended)

Use the #[telegram_app(auto_init)] macro:

use telegram_webapp_sdk::telegram_app;
use yew::prelude::*;

#[telegram_app(auto_init)]
#[function_component(App)]
fn app() -> Html {
    html! {
        <h1>{ "Hello Telegram!" }</h1>
    }
}

The macro wraps your component and calls init_sdk() before rendering.

Manual

Call init_sdk() or try_init_sdk() explicitly:

use telegram_webapp_sdk::core::init::{init_sdk, try_init_sdk};

// Panics if initialization fails
fn main() {
    init_sdk().expect("Failed to initialize SDK");
    // ... your app code
}

// Graceful error handling
fn main() {
    match try_init_sdk() {
        Ok(true) => {
            println!("SDK initialized");
            // ... your app code
        }
        Ok(false) => {
            println!("Not in Telegram environment");
            // ... fallback behavior
        }
        Err(e) => {
            eprintln!("Initialization error: {}", e);
        }
    }
}

Initialization Errors

The SDK provides typed errors for debugging:

use telegram_webapp_sdk::core::init::{try_init_sdk, InitError};

match try_init_sdk() {
    Ok(_) => { /* success */ }
    Err(InitError::WindowUnavailable) => {
        eprintln!("Browser window not available");
    }
    Err(InitError::TelegramUnavailable) => {
        eprintln!("window.Telegram is undefined - did you include the Telegram script?");
    }
    Err(InitError::WebAppUnavailable) => {
        eprintln!("Telegram.WebApp is undefined");
    }
    Err(InitError::InitDataParseFailed(msg)) => {
        eprintln!("Failed to parse init data: {}", msg);
    }
    Err(InitError::ThemeParamsParseFailed(msg)) => {
        eprintln!("Failed to parse theme: {}", msg);
    }
    Err(InitError::ContextInitFailed(msg)) => {
        eprintln!("Context initialization failed: {}", msg);
    }
}

Global Context

What is TelegramContext?

The SDK maintains a global context that holds all Telegram-provided data:

pub struct TelegramContext {
    pub init_data: TelegramInitData,         // User, chat, auth data
    pub theme_params: TelegramThemeParams,   // Color scheme
    pub raw_init_data: String,               // For server validation
}

This context is initialized once and stored in thread-local storage:

┌─────────────────────────────────────────┐
│      Your WebApp Process                 │
│                                          │
│  ┌────────────────────────────────────┐ │
│  │   Thread-Local Storage              │ │
│  │                                     │ │
│  │   TelegramContext (OnceCell)       │ │
│  │   ├── init_data                    │ │
│  │   ├── theme_params                 │ │
│  │   └── raw_init_data                │ │
│  └────────────────────────────────────┘ │
│                                          │
│  All components access same context     │
└─────────────────────────────────────────┘

Accessing the Context

use telegram_webapp_sdk::core::context::TelegramContext;

// Access context with a closure
let user_id = TelegramContext::get(|ctx| {
    ctx.init_data.user.as_ref().map(|u| u.id)
});

match user_id {
    Some(Some(id)) => println!("User ID: {}", id),
    Some(None) => println!("No user data"),
    None => println!("Context not initialized"),
}

Raw Init Data

The raw_init_data field contains the URL-encoded string from Telegram. Send this to your backend for validation:

use telegram_webapp_sdk::core::context::TelegramContext;

match TelegramContext::get_raw_init_data() {
    Ok(raw) => {
        // Send to backend
        send_to_server(&raw).await;
    }
    Err(e) => eprintln!("Error: {}", e),
}

Init Data Structure

TelegramInitData

The init_data contains information about the user, chat, and session:

pub struct TelegramInitData {
    pub query_id: Option<String>,           // Query ID for answerWebAppQuery
    pub user: Option<TelegramUser>,         // Current user
    pub receiver: Option<TelegramUser>,     // Receiver in private chat
    pub chat: Option<TelegramChat>,         // Current chat
    pub chat_type: Option<String>,          // Chat type: sender, private, group, supergroup, channel
    pub chat_instance: Option<String>,      // Chat instance ID
    pub start_param: Option<String>,        // Deep link parameter
    pub can_send_after: Option<i64>,        // Timestamp when user can send messages
    pub auth_date: i64,                     // Authentication date (Unix timestamp)
    pub hash: String,                       // Data hash for validation
    pub signature: Option<String>,          // Optional signature
}

TelegramUser

User information from Telegram:

pub struct TelegramUser {
    pub id: i64,                               // Unique user ID
    pub is_bot: Option<bool>,                  // Is this user a bot?
    pub first_name: String,                    // First name (always present)
    pub last_name: Option<String>,             // Last name
    pub username: Option<String>,              // Username (@username)
    pub language_code: Option<String>,         // IETF language tag (e.g., "en", "ru")
    pub is_premium: Option<bool>,              // Is Telegram Premium subscriber?
    pub added_to_attachment_menu: Option<bool>, // Added bot to attachment menu?
    pub allows_write_to_pm: Option<bool>,      // Allows writing to PM?
    pub photo_url: Option<String>,             // Profile photo URL
}

TelegramChat

Chat information (when app is opened in a chat context):

pub struct TelegramChat {
    pub id: i64,                    // Unique chat ID
    pub r#type: String,             // Type: group, supergroup, channel
    pub title: String,              // Chat title
    pub username: Option<String>,   // Username (@channel)
    pub photo_url: Option<String>,  // Chat photo URL
}

Example: Accessing User Data

use telegram_webapp_sdk::TelegramWebApp;

let webapp = TelegramWebApp::new();
let init_data = webapp.init_data_unsafe();

if let Some(user) = &init_data.user {
    println!("User ID: {}", user.id);
    println!("Name: {}", user.first_name);

    if let Some(username) = &user.username {
        println!("Username: @{}", username);
    }

    if let Some(is_premium) = user.is_premium {
        if is_premium {
            println!("Premium user!");
        }
    }
}

Important: init_data_unsafe() returns unvalidated data. Always validate on your backend:

// Client-side: Send raw data to backend
let raw = TelegramContext::get_raw_init_data()?;
send_to_backend(&raw).await;

// Server-side: Validate with init-data-rs
use init_data_rs::{validate, InitData};

let init_data: InitData = validate(&raw, bot_token, Some(3600))?;
// Now you can trust the data

Theme Parameters

TelegramThemeParams

Telegram provides its color scheme so your app can match the user's theme:

pub struct TelegramThemeParams {
    pub bg_color: Option<String>,                 // Background color (#RRGGBB)
    pub text_color: Option<String>,               // Text color
    pub hint_color: Option<String>,               // Hint text color
    pub link_color: Option<String>,               // Link color
    pub button_color: Option<String>,             // Button background
    pub button_text_color: Option<String>,        // Button text
    pub secondary_bg_color: Option<String>,       // Secondary background
    pub header_bg_color: Option<String>,          // Header background
    pub accent_text_color: Option<String>,        // Accent text
    pub section_bg_color: Option<String>,         // Section background
    pub section_header_text_color: Option<String>, // Section header text
    pub subtitle_text_color: Option<String>,      // Subtitle text
    pub destructive_text_color: Option<String>,   // Destructive action text
}

Using Theme Colors

use telegram_webapp_sdk::TelegramWebApp;

let webapp = TelegramWebApp::new();

// Access theme from context
let theme = TelegramContext::get(|ctx| ctx.theme_params.clone());

if let Some(theme_params) = theme {
    if let Some(bg_color) = theme_params.bg_color {
        // Apply background color
        set_background_color(&bg_color);
    }

    if let Some(button_color) = theme_params.button_color {
        // Style buttons
        style_button(&button_color);
    }
}

CSS Integration

Apply theme colors to CSS variables:

use telegram_webapp_sdk::core::types::theme_params::TelegramThemeParams;

impl TelegramThemeParams {
    pub fn into_css_vars(&self) -> Vec<(String, String)> {
        let mut vars = Vec::new();

        if let Some(ref bg_color) = self.bg_color {
            vars.push(("--tg-theme-bg-color".to_string(), bg_color.clone()));
        }
        if let Some(ref text_color) = self.text_color {
            vars.push(("--tg-theme-text-color".to_string(), text_color.clone()));
        }
        // ... all other colors

        vars
    }
}

Then in your CSS:

body {
    background-color: var(--tg-theme-bg-color, #ffffff);
    color: var(--tg-theme-text-color, #000000);
}

button {
    background-color: var(--tg-theme-button-color, #0088cc);
    color: var(--tg-theme-button-text-color, #ffffff);
}

Launch Parameters

What are Launch Parameters?

Launch parameters come from URL query parameters and tell you how the app was launched:

pub struct LaunchParams {
    pub tg_web_app_platform: Option<String>,       // Platform: android, ios, web, etc.
    pub tg_web_app_version: Option<String>,        // Telegram version
    pub tg_web_app_start_param: Option<String>,    // Deep link parameter
    pub tg_web_app_show_settings: Option<bool>,    // Show settings button?
    pub tg_web_app_bot_inline: Option<bool>,       // Opened from inline mode?
}

Reading Launch Parameters

use telegram_webapp_sdk::core::context::get_launch_params;

match get_launch_params() {
    Ok(params) => {
        if let Some(platform) = params.tg_web_app_platform {
            println!("Platform: {}", platform); // "android", "ios", "web"
        }

        if let Some(version) = params.tg_web_app_version {
            println!("Telegram version: {}", version); // "7.0", "7.1", etc.
        }

        if let Some(start_param) = params.tg_web_app_start_param {
            // Deep link parameter from bot link:
            // https://t.me/yourbot?start=PARAM
            println!("Start parameter: {}", start_param);
        }

        if let Some(true) = params.tg_web_app_show_settings {
            // Show settings UI
        }
    }
    Err(e) => eprintln!("Failed to get launch params: {:?}", e),
}

Platform-Specific Logic

use telegram_webapp_sdk::core::context::get_launch_params;

let params = get_launch_params()?;

match params.tg_web_app_platform.as_deref() {
    Some("ios") => {
        // iOS-specific behavior
        println!("Running on iOS");
    }
    Some("android") => {
        // Android-specific behavior
        println!("Running on Android");
    }
    Some("web") | Some("weba") => {
        // Web version (desktop or mobile browser)
        println!("Running in browser");
    }
    Some("tdesktop") => {
        // Telegram Desktop
        println!("Running in Telegram Desktop");
    }
    Some("macos") => {
        // macOS app
        println!("Running on macOS");
    }
    _ => {
        println!("Unknown platform");
    }
}

Deep Linking

Use start_param for deep linking into specific app sections:

// In your bot, send a link like:
// https://t.me/yourbot/app?startapp=product_123

let params = get_launch_params()?;

if let Some(start_param) = params.tg_web_app_start_param {
    match start_param.as_str() {
        s if s.starts_with("product_") => {
            let product_id = s.strip_prefix("product_").unwrap();
            navigate_to_product(product_id);
        }
        "settings" => {
            navigate_to_settings();
        }
        _ => {
            navigate_to_home();
        }
    }
}

The TelegramWebApp Struct

Main API Entry Point

TelegramWebApp is the primary interface to all Telegram features:

use telegram_webapp_sdk::TelegramWebApp;

let webapp = TelegramWebApp::new();

Key Methods

// Lifecycle
webapp.ready();                    // Tell Telegram the app is ready
webapp.close();                    // Close the WebApp
webapp.expand_viewport();          // Expand to full height

// UI
webapp.set_header_color("#FF0000");
webapp.set_background_color("#FFFFFF");
webapp.set_bottom_bar_color("#000000");

// Dialogs
webapp.show_alert("Hello!");
webapp.show_confirm("Are you sure?", callback);
webapp.show_popup(params);

// Buttons
webapp.main_button().set_text("Submit");
webapp.main_button().show();
webapp.main_button().on_click(callback);

// Data
let init_data = webapp.init_data_unsafe();
let theme = webapp.theme_params();

// Navigation
webapp.open_link("https://example.com");
webapp.open_telegram_link("https://t.me/telegram");

Example: Complete App Flow

use telegram_webapp_sdk::{telegram_app, TelegramWebApp};
use yew::prelude::*;

#[telegram_app(auto_init)]
#[function_component(App)]
fn app() -> Html {
    let webapp = TelegramWebApp::new();

    // 1. Tell Telegram we're ready
    use_effect_with((), move |_| {
        webapp.ready();
        webapp.expand_viewport();
        || ()
    });

    // 2. Set up main button
    use_effect_with((), move |_| {
        webapp.main_button().set_text("Submit Order");
        webapp.main_button().show();
        webapp.main_button().on_click(Callback::from(move |_| {
            webapp.show_alert("Order submitted!");
            webapp.close();
        }));
        || ()
    });

    // 3. Apply theme
    use_effect_with((), move |_| {
        if let Some(bg_color) = webapp.theme_params().bg_color {
            webapp.set_background_color(&bg_color);
        }
        || ()
    });

    // 4. Render UI
    html! {
        <div>
            <h1>{ "My Telegram App" }</h1>
            {
                if let Some(user) = &webapp.init_data_unsafe().user {
                    html! {
                        <p>{ format!("Hello, {}!", user.first_name) }</p>
                    }
                } else {
                    html! { <p>{ "Loading..." }</p> }
                }
            }
        </div>
    }
}

Type Safety

Why Types Matter

Rust's type system prevents entire classes of runtime errors:

// Compiler guarantees user.id is always i64
let user_id: i64 = webapp.init_data_unsafe().user.unwrap().id;

// Optional fields are explicit - won't compile without handling None
let username: Option<String> = webapp.init_data_unsafe().user.unwrap().username;

match username {
    Some(name) => println!("Username: @{}", name),
    None => println!("No username"),
}

Common Patterns

// Safe unwrapping with defaults
let user_name = webapp
    .init_data_unsafe()
    .user
    .as_ref()
    .map(|u| u.first_name.clone())
    .unwrap_or_else(|| "Guest".to_string());

// Chaining Options
let is_premium = webapp
    .init_data_unsafe()
    .user
    .as_ref()
    .and_then(|u| u.is_premium)
    .unwrap_or(false);

// Pattern matching
match &webapp.init_data_unsafe().user {
    Some(user) if user.is_premium == Some(true) => {
        println!("Premium user: {}", user.first_name);
    }
    Some(user) => {
        println!("Regular user: {}", user.first_name);
    }
    None => {
        println!("No user data");
    }
}

Next Steps

Now that you understand the core concepts, learn how to integrate with your framework:

Or explore the complete API:

Clone this wiki locally