· 5 min read

Practical Tutorial: Using Telegram Mini App Cloud Storage with the Latest Telegram Platform Updates

Telegram’s recent Mini App updates make it easier to build stateful, app-like experiences without forcing users through separate auth or backend-heavy flows. This tutorial shows Telegram developers how to use Cloud Storage, secure initialization data, and a clean client-server pattern to ship a useful feature quickly.

Telegram’s Mini App ecosystem has been moving fast, and one of the most useful recent developments for developers is the combination of improved Web App capabilities with Cloud Storage for lightweight persistence. If you build Telegram-powered products, this matters because it lets you create smoother, stateful experiences without immediately standing up a full user-profile system.

In this tutorial, we’ll build a small but realistic pattern: a Mini App that stores a user’s preferred settings in Telegram Cloud Storage, verifies the user securely on the backend, and syncs those preferences when needed.

This is useful when you need to remember things like:

  • theme choice
  • notification preferences
  • onboarding completion
  • filter or sort settings
  • draft state for a lightweight workflow

We’ll focus on the software engineering side: architecture, data flow, validation, and integration details that matter in production.

What’s new and why it matters

Telegram Mini Apps have matured into a better platform for app-like workflows inside chat. The practical development win is that you can now treat the Mini App more like a real client application rather than a one-off web page.

The most relevant pieces here are:

  • Telegram WebApp init data for identity and session bootstrap
  • Cloud Storage for small client-side state tied to the Telegram account
  • MainButton / BackButton / theme params for native-feeling UX
  • Backend verification to avoid trusting the client blindly

For Telegram developers, this combination reduces friction in onboarding and state persistence. You can keep the first interaction inside Telegram, defer account creation, and still preserve a meaningful user experience.

Architecture overview

We’ll use a simple split:

  • Client Mini App: handles UI and reads/writes Cloud Storage
  • Backend API: verifies Telegram init data and stores any server-owned data

Why split it this way?

Because Cloud Storage is ideal for small preference data, but not for anything sensitive or business-critical. Anything that affects entitlements, payments, moderation, or security should be validated and stored on the server.

Data flow

  1. User opens the Mini App from a bot.
  2. Telegram injects WebApp initData into the page.
  3. The app sends initData to your backend.
  4. Backend verifies the signature using your bot token.
  5. Frontend reads preferences from Cloud Storage.
  6. If needed, frontend updates preferences in Cloud Storage.
  7. Backend separately stores any authoritative user data.

Step 1: Set up the Mini App

Your Mini App is just a web app served over HTTPS and connected to your bot.

Frontend bootstrapping

Here’s a minimal client setup using the Telegram Web App API:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://telegram.org/js/telegram-web-app.js"></script>
    <title>Preferences</title>
  </head>
  <body>
    <h1>Notification Preferences</h1>
    <label>
      <input type="checkbox" id="dailySummary" />
      Daily summary
    </label>
    <button id="saveBtn">Save</button>

    <script>
      const tg = window.Telegram.WebApp;
      tg.ready();

      const dailySummary = document.getElementById('dailySummary');
      const saveBtn = document.getElementById('saveBtn');

      async function loadPreference() {
        const value = await tg.CloudStorage.getItem('dailySummary');
        dailySummary.checked = value === 'true';
      }

      async function savePreference() {
        await tg.CloudStorage.setItem('dailySummary', String(dailySummary.checked));
        tg.showAlert('Preference saved');
      }

      saveBtn.addEventListener('click', savePreference);
      loadPreference();
    </script>
  </body>
</html>

Notes on the example

A few engineering details are easy to miss:

  • Cloud Storage values are string-based, so convert booleans explicitly.
  • Keep keys stable and namespaced, especially if your Mini App grows.
  • Don’t treat Cloud Storage as a secure database.
  • Call tg.ready() early so Telegram can optimize the app container.

A better key scheme for a real app would be something like:

  • prefs.dailySummary
  • prefs.theme
  • onboarding.completed

Step 2: Verify Telegram users on the backend

Even if your client can store preferences, you still need a trustworthy identity layer.

Telegram Mini Apps pass signed init data that you should verify server-side. This prevents a user from impersonating another user by editing client-side fields.

Example backend verification in Node.js

import crypto from 'crypto';
import express from 'express';

const app = express();
app.use(express.json());

function verifyTelegramInitData(initData, botToken) {
  const params = new URLSearchParams(initData);
  const hash = params.get('hash');
  if (!hash) return false;

  params.delete('hash');

  const dataCheckString = [...params.entries()]
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([key, value]) => `${key}=${value}`)
    .join('\n');

  const secretKey = crypto
    .createHmac('sha256', 'WebAppData')
    .update(botToken)
    .digest();

  const calculatedHash = crypto
    .createHmac('sha256', secretKey)
    .update(dataCheckString)
    .digest('hex');

  return calculatedHash === hash;
}

app.post('/api/session', (req, res) => {
  const { initData } = req.body;

  if (!verifyTelegramInitData(initData, process.env.BOT_TOKEN)) {
    return res.status(401).json({ error: 'Invalid Telegram session' });
  }

  const params = new URLSearchParams(initData);
  const userJson = params.get('user');
  const user = userJson ? JSON.parse(userJson) : null;

  if (!user) {
    return res.status(400).json({ error: 'Missing user data' });
  }

  res.json({ ok: true, telegramUserId: user.id });
});

app.listen(3000, () => console.log('Server running on :3000'));

Important production note

This example shows the pattern, but you should also:

  • check auth_date age and reject stale sessions
  • handle parsing carefully
  • store only the Telegram user ID and verified claims you need
  • avoid trusting any profile field that is not part of your verification logic

Step 3: Sync preferences with your server

Cloud Storage is convenient, but sometimes you want to keep a server copy too. That gives you a fallback when users switch devices or you need analytics.

A simple sync strategy is:

  • read from Cloud Storage at startup
  • POST changes to your backend after verification
  • let the server store the canonical copy if the preference matters

Client sync example

async function syncPreferenceToServer(key, value, initData) {
  const response = await fetch('/api/preferences', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      initData,
      key,
      value,
    }),
  });

  if (!response.ok) {
    throw new Error('Sync failed');
  }
}

async function savePreference() {
  const value = String(dailySummary.checked);
  await tg.CloudStorage.setItem('prefs.dailySummary', value);
  await syncPreferenceToServer('prefs.dailySummary', value, tg.initData);
  tg.showAlert('Preference saved locally and on the server');
}

Backend preference endpoint

app.post('/api/preferences', (req, res) => {
  const { initData, key, value } = req.body;

  if (!verifyTelegramInitData(initData, process.env.BOT_TOKEN)) {
    return res.status(401).json({ error: 'Invalid Telegram session' });
  }

  if (typeof key !== 'string' || typeof value !== 'string') {
    return res.status(400).json({ error: 'Invalid payload' });
  }

  // Lookup telegram user ID from verified initData.
  // Then persist the preference in your DB.
  // Example:
  // await db.preferences.upsert({ telegramUserId, key, value })

  res.json({ ok: true });
});

Step 4: Use Telegram-specific UX patterns

This is where many generic web apps fall short. If you want a good Telegram Mini App, use the platform’s UI conventions.

MainButton instead of a random submit button

If the app’s primary action is “Save,” make it feel native:

tg.MainButton.setText('Save preferences');
tg.MainButton.show();
tg.MainButton.onClick(savePreference);

Theme-aware styling

Telegram passes theme parameters so your app can match light and dark mode automatically.

const bg = tg.themeParams.bg_color || '#ffffff';
const text = tg.themeParams.text_color || '#000000';
document.body.style.backgroundColor = bg;
document.body.style.color = text;

Close behavior

If the user finishes a short task, close the Mini App deliberately:

tg.close();

That small detail improves the flow significantly for chat-based experiences.

Step 5: Avoid the common mistakes

Here are the mistakes I see most often in Telegram Mini App implementations.

1. Using Cloud Storage for sensitive data

Do not store secrets, tokens, payment state, or permissions in Cloud Storage. Treat it as convenience storage, not security storage.

2. Trusting the client session

Never accept a Telegram user ID from the frontend without verifying initData on the server.

3. Ignoring stale auth data

A signed session should still have a lifetime policy. Enforce it.

4. Building a web app that ignores Telegram context

Telegram users expect a smooth, contained interaction. Use the available buttons, theme, and close behavior.

5. Overengineering the first version

For many bots, you only need a few preferences and a verified session. Start small and expand when a real need appears.

A practical implementation checklist

Before shipping, verify the following:

  • your Mini App is served over HTTPS
  • backend verifies Telegram init data
  • auth_date is checked
  • Cloud Storage keys are namespaced
  • sensitive data stays on the server
  • theme colors are respected
  • the primary action uses Telegram-native UI
  • user state is resilient across refreshes and device changes

When to use Cloud Storage vs your database

Use this rule of thumb:

Use Cloud Storage for

  • UI preferences
  • temporary onboarding state
  • small drafts
  • local filters
  • feature flags that are not security-sensitive

Use your backend database for

  • permissions
  • subscriptions
  • payment status
  • audit logs
  • any data needed across devices as a source of truth

If you’re building a production Telegram product, you often need both. Cloud Storage improves UX, while the backend keeps your data trustworthy.

Conclusion

The recent evolution of Telegram Mini Apps makes it much easier to build polished, stateful experiences inside Telegram without creating unnecessary friction. For Telegram developers, Cloud Storage is especially useful when paired with secure backend verification: it gives you fast client-side persistence for preferences while keeping authoritative data where it belongs.

If you keep the architecture simple, validate init data on the server, and use Telegram-native UI patterns, you can ship features that feel integrated rather than bolted on.

That’s the key engineering shift: not just making a web app that runs inside Telegram, but building a Telegram-first product with the right boundary between convenience and trust.

← All posts