Legan Studio
Все статьи
~ 5 мин чтения

Telegram Cloud Storage в Mini App: хранение без сервера

Как использовать tgWebApp.CloudStorage для пер-юзерных настроек в Mini App: API, лимиты, паттерны кеширования и синхронизация с бэкендом.

  • Telegram
  • Mini App
  • Cloud Storage
  • Frontend

Telegram CloudStorage — это key-value хранилище в API Telegram Mini Apps, привязанное к паре «бот + пользователь». Данные хранятся на стороне Telegram, синхронизируются между всеми устройствами юзера и доступны без сервера. Для маленьких настроек, состояния UI и кешей это идеальная замена localStorage и IndexedDB.

Разберём API, лимиты, паттерны использования, как сочетать CloudStorage с серверным API и какие сценарии она закрывает без бэкенда.

Что такое CloudStorage

CloudStorage появился в Bot API 6.9 (2023). Доступен через window.Telegram.WebApp.CloudStorage — асинхронный key-value API:

const cs = window.Telegram.WebApp.CloudStorage;

cs.setItem("theme", "dark", (err, ok) => { /* ... */ });
cs.getItem("theme", (err, value) => console.log(value));
cs.removeItem("theme", (err) => { /* ... */ });
cs.getKeys((err, keys) => console.log(keys));
cs.getItems(["theme", "lang"], (err, dict) => console.log(dict));
cs.removeItems(["theme", "lang"], (err) => { /* ... */ });

API колбэк-стиль. В современных приложениях оборачивают в Promise:

const setItem = (k: string, v: string) =>
  new Promise<boolean>((resolve, reject) =>
    cs.setItem(k, v, (err, ok) => (err ? reject(err) : resolve(ok)))
  );

Лимиты

ПараметрЛимит
Максимум ключей1024 на (бот, юзер)
Длина ключа1–128 символов, [A-Za-z0-9_-]
Длина значения0–4096 символов
Общий размер~4 МБ суммарно
Скорость записи~10 операций/сек (мягкий лимит)

Это не БД: 4 МБ — потолок. Для каталогов, сообщений и больших объектов CloudStorage не подходит.

Что туда класть

Хорошие кейсы:

  • UI-состояние: тема, язык, выбранная вкладка, последний поиск.
  • Корзина в маленьком магазине: до 50 товаров.
  • Настройки уведомлений: что и когда присылать.
  • Прогресс прохождения: какой урок открыт, какой бейдж получен.
  • Кеш профиля: чтобы Mini App открывался мгновенно без запроса к бэкенду.
  • Черновики форм.

Плохие кейсы — JWT-токены (они должны быть в HttpOnly cookie или памяти), пароли, личные данные клиентов в b2b-сценариях.

Паттерн: «офлайн-первый» Mini App

Mini App должен открываться мгновенно даже на медленном интернете. Паттерн:

async function bootstrap() {
  // 1. Поднимаем UI из CloudStorage сразу
  const cached = await getItem("user_profile_v1");
  if (cached) {
    renderApp(JSON.parse(cached));
  } else {
    renderSkeleton();
  }
  // 2. Параллельно подтягиваем свежие данные
  try {
    const fresh = await api.getProfile();
    await setItem("user_profile_v1", JSON.stringify(fresh));
    renderApp(fresh);
  } catch {
    /* остаёмся на кешированном */
  }
}

Юзер видит экран за 50 мс вместо 500 мс «спиннер → загрузка». TTI Mini App падает в 5–10 раз.

Синхронизация между устройствами

CloudStorage синхронизируется автоматически между всеми клиентами Telegram юзера. Если на iPhone положили cart_v1, через 1–3 секунды она появится на Mac и Android.

Это даёт «бесплатный» continuity: юзер начал заказ на телефоне, продолжил на компьютере, корзина та же.

Версионирование схемы

CloudStorage не миграбелен: данные старого формата останутся как есть. Поэтому версионируйте ключи:

const KEY = "cart_v2";  // v1 был с другой схемой

async function getCart() {
  const raw = await getItem(KEY);
  if (raw) return JSON.parse(raw);
  // мигрируем со старой версии один раз
  const old = await getItem("cart_v1");
  if (old) {
    const migrated = migrate(JSON.parse(old));
    await setItem(KEY, JSON.stringify(migrated));
    await removeItem("cart_v1");
    return migrated;
  }
  return defaultCart();
}

Сжатие больших значений

4096 символов на значение — мало для JSON-объекта с 50 полями. Решение — JSON + LZ-string:

import LZString from "lz-string";

const setBig = (k: string, obj: any) =>
  setItem(k, LZString.compressToBase64(JSON.stringify(obj)));

const getBig = async (k: string) => {
  const raw = await getItem(k);
  return raw ? JSON.parse(LZString.decompressFromBase64(raw)!) : null;
};

Сжатие даёт 3–5х экономию места на типичном JSON.

Сравнение с альтернативами

ХранилищеОбъёмКросс-устройствоЖизненный цикл
CloudStorage~4 МБдапока бот не удалён
localStorage~5 МБ на доменнетпока юзер не очистит браузер
IndexedDB~50–100 МБнетто же
Бэкенд (PostgreSQL)сколько угоднодаконтролируете вы

Для микро-настроек CloudStorage. Для пользовательских данных среднего объёма — бэкенд. Для больших офлайн-кешей — IndexedDB на стороне Mini App.

Гонки и идемпотентность

Если юзер на двух устройствах одновременно меняет один ключ, побеждает последний setItem — без conflict resolution. Поэтому критичные данные пишите через бэкенд, а CloudStorage — для локального состояния, где гонка некритична.

Безопасность

CloudStorage доступен только конкретной паре (бот, юзер). Другие боты, другие юзеры этих данных не видят. Telegram шифрует их в транспорте и хранит на своих серверах.

Тем не менее, не кладите туда:

  • Платёжные карты, CVV.
  • Пароли в plain text.
  • Чужие данные (адреса, телефоны других юзеров).
  • Бизнес-секреты компании.

Топ-5 ошибок

  1. Кладут в CloudStorage большие массивы (>4 КБ) — setItem тихо обрезает или падает.
  2. Не оборачивают callback API в Promise — лапша колбэков по всему коду.
  3. Используют как первичное хранилище без бэкапа в БД — теряют данные при удалении бота юзером.
  4. Пишут JWT в CloudStorage — он становится exfiltrable XSS-атакой.
  5. Не версионируют ключи — старая схема сосуществует с новой и ломает приложение.

Итого

CloudStorage — лучший friend Mini App-разработчика для локального состояния, кешей и пользовательских настроек. Лимит 4 МБ суммарно, синхронизация между устройствами «из коробки», нулевой бэкенд-оверхед. Используйте как кеш и UI-state, не как primary storage; версионируйте ключи; сжимайте большие значения; и не кладите туда секреты — это всё, что нужно знать.

Частые вопросы

Что происходит с данными, если юзер удалил бота?

Telegram гарантированно удаляет всё CloudStorage этой пары (bot, user) в течение 30 дней. Если юзер вернётся и снова напишет /start, он получит чистый CloudStorage. Поэтому критичные данные дублируйте на бэкенд, привязывая к telegram_id.

Можно ли читать CloudStorage с бэкенда?

Нет. CloudStorage доступен только из Mini App в контексте конкретного юзера. С бэкенда вы можете только сказать Mini App «отправь мне свои данные через sendData», и Mini App сам выгрузит CloudStorage и отправит. Это by design: данные принадлежат юзеру.

Как обновить значение атомарно?

Никак — CloudStorage не имеет CAS (compare-and-swap). Если нужно атомарное обновление счётчика или списка, держите его на бэкенде, а CloudStorage используйте только как кеш для UI. Альтернатива — versioning внутри значения и retry-цикл при конфликте.

Сколько раз в секунду можно вызывать setItem?

Документированного лимита нет, но на практике >10 операций/сек начинают давать ошибки или задержки. Дебаунсите запись: накапливаете изменения 200–500 мс и пишете батчем через setItems.

Работает ли CloudStorage в desktop-клиентах?

Да, начиная с Telegram Desktop 4.10+. На старых десктопах API возвращает unavailable, и Mini App должен это обработать (fallback на localStorage или бэкенд). Проверка — Telegram.WebApp.isVersionAtLeast("6.9").

Можно ли шифровать значения дополнительно?

Можно, но осмысленно только для специфичных b2b-кейсов. Ключ шифрования всё равно либо хранится в CloudStorage (бесполезно), либо запрашивается у бэкенда (тогда проще класть зашифрованное значение сразу на бэкенд). В 99% случаев нативной защиты Telegram достаточно.

Как мигрировать с localStorage на CloudStorage?

В первый запуск читаете localStorage, копируете в CloudStorage, очищаете localStorage. После этого работаете только с CloudStorage. Учтите, что localStorage был на одном устройстве, а CloudStorage синхронизируется — могут возникнуть конфликты, если юзер запустил Mini App параллельно на двух устройствах. Делайте merge по timestamp.