Appearance
Settings
Current version: v0.0.4
telegram-bot-essentials/settings (namespace TelegramBotEssentials\Settings) adds a per-bot, typed key/value settings system to Essence. Every other module in the ecosystem (Billing, User Wallet, Affiliates, the gateways) registers its own configurable options through this package rather than inventing its own settings storage.
Installation
bash
composer require telegram-bot-essentials/settings
php artisan migrateThe package auto-registers a locale setting out of the box, letting each bot pick its own admin/reply language independently (see Locale switching below).
Core Concepts
The Setting DTO
A setting is declared once, in a service provider's boot(), as a TelegramBotEssentials\Settings\DTOs\Setting:
php
use TelegramBotEssentials\Settings\DTOs\Setting;
use TelegramBotEssentials\Settings\Enums\SettingType;
settings()->addSetting(new Setting(
key: 'billing.user_wallet.status',
label: fn () => __('my-package::settings.wallet_status.label'),
type: SettingType::CHECKBOX,
default: false,
));| Parameter | Type | Notes |
|---|---|---|
key | string | Dot-namespaced identifier, e.g. billing.user_wallet.currency |
label | string|Closure | Resolved lazily — use a closure to defer translation lookups |
type | SettingType | Controls validation, storage, and the admin UI widget |
default | mixed | Returned when no BotSetting row exists yet |
options | array|Closure|null | Required for SELECT, ENUM, MULTISELECT |
description | string|Closure|null | Shown in the admin settings UI |
SettingType
src/Enums/SettingType.php defines the supported widget/storage types:
| Type | Storage | Validation |
|---|---|---|
TEXT | raw string | nullable|string |
NUMBER | raw string | nullable|numeric |
CHECKBOX | raw string ('1'/'0') | nullable|boolean |
SENSITIVE | encrypted via encrypt()/decrypt() | nullable|string |
SELECT | one of options keys | nullable|in:<option keys> |
ENUM | one of options values | nullable|in:<option values> |
MULTISELECT | comma-joined string | nullable|array |
DIRECTORY | — (UI grouping only, no dedicated storage branch) | — |
SENSITIVE is the type to reach for with API keys, gateway secrets, or anything else you don't want sitting in plaintext in the bot_settings table.
The settings() helper
Everything is read and written through the global settings() helper (src/helpers.php), which resolves the Settings singleton:
php
// Read (falls back to the Setting's default if no row exists yet)
$locale = settings()->get('locale');
$walletEnabled = settings()->get('billing.user_wallet.status');
// Write (validated against the type's rules before saving)
settings()->set('billing.user_wallet.status', true);
// Introspect a declared setting (for building admin UI)
$setting = settings()->getSetting('billing.user_wallet.status');
$setting->getLabel();
$setting->getTypeEmoji(); // ✅ / ❌ / 🔒 / 🔢 / 💬 / 🧩 / 📋 / 🗃️
// List everything registered, for a settings menu
settings()->getSettings(); // Collection<string, Setting>get()/set() are always scoped to wHook()->bot()->id — settings are per-bot, not global to the application. This is why other modules read settings inside request-time closures (see How other modules use this) rather than caching the value at boot.
Storage
One row per (bot_id, key) pair in bot_settings (model BotSetting, tenant-scoped via BelongsToTenant). Values are always stored as strings/null and cast back to the appropriate shape by Settings::get() based on the setting's declared SettingType.
Locale switching
The package ships a locale setting (SettingType::SELECT) whose options come from LocaleRegistry::selectOptions() — it scans the host app's translation files (via Essence's TranslationScanner) and shows each supported locale with a live translation-completeness percentage, e.g. فارسی 412/420 (98%).
A listener, SetBotLocale, runs on BotWebhookInitialized and calls App::setLocale(settings()->get('locale') ?? config('app.locale')) — so every bot can run in a different language without any per-request wiring in your own code.
Admin UI
The package registers its own CallbackQuery, StateAnswer, ReplyKey, and Feature (Telegram/CallbackQueries/Admin/BotSettingsQuery.php, etc.) that render a generic settings menu from whatever is currently in the registry — you get a working admin settings screen for free as soon as you addSetting() something, with no bespoke UI code required per setting.
How other modules use this
This is the pattern used throughout the ecosystem for feature flags and per-bot configuration — declare the setting once in your service provider's boot(), then check it lazily wherever it matters:
php
// TbeUserWalletServiceProvider::boot()
settings()->addSetting(new Setting(
key: 'billing.user_wallet.status',
label: fn () => __('tbe-user-wallet::settings.status.label'),
type: SettingType::CHECKBOX,
default: false,
));
// Elsewhere, e.g. registering a conditional sort — evaluated per request, not at boot:
botUserSorts()->addSort(new BotUserSort(
// ...
active: fn () => settings()->get('billing.user_wallet.status'),
));Evaluate lazily
Because settings are per-bot and boot() runs once per application (not per bot/tenant), never resolve settings()->get(...) directly inside boot() to decide whether to register something — the bot context isn't available yet. Instead, wrap the check in a Closure (an active callback, a conditional inside a handler method, etc.) so it's evaluated at request time, once wHook()->bot() is resolved.