A comprehensive boilerplate for building Telegram Mini Web Apps with modern React stack
🎯 Features • 🚀 Quick Start • 📚 Documentation • 🛠️ API Reference • 🌐 Deploy
- ⚡ Vite - Lightning fast build tool and dev server
- ⚛️ React 18 - Latest React with concurrent features
- 🎯 TypeScript - Full type safety and IntelliSense
- 🎨 Tailwind CSS - Utility-first CSS framework
- 🔗 React Router - Client-side routing
- 🔄 React Query - Powerful data fetching and caching
- ✅ Complete API Coverage - All Telegram WebApp methods
- ✅ Latest Features - Gyroscope, Accelerometer, Device Orientation
- ✅ Theme Integration - Automatic light/dark mode
- ✅ Haptic Feedback - Rich tactile feedback
- ✅ Cloud Storage - Persistent data storage
- ✅ Biometric Auth - Fingerprint/Face recognition
- ✅ QR Scanner - Built-in QR code scanning
- ✅ Location Services - GPS location access
- ✅ Clipboard Access - Read/write clipboard
- ✅ Fullscreen Mode - Immersive experience
- ✅ Story Sharing - Share to Telegram Stories
- 🌙 Dark/Light Mode - Automatic theme switching
- 📱 Responsive Design - Works on all screen sizes
- 🎭 Modern Components - Beautiful, accessible UI components
- ⚡ Smooth Animations - Framer Motion powered
- 🎯 Interactive Examples - Live feature demonstrations
Try out the Telegram Mini Web App boilerplate live!
Scan the QR code below or open the demo link in your telegram.
- Node.js 18+
- npm/yarn/pnpm
- Telegram Bot (create via @BotFather)
# Clone the repository
git clone https://github.com/vishal-1756/telegram-miniapp-boilerplate.git
cd telegram-miniapp-boilerplate
# Install dependencies
npm install
# or
yarn install
# or
pnpm install
# Copy environment template
cp .env.example .env
# Edit your environment variables
nano .env
# API Configuration
VITE_API_BASE_URL=https://your-api-domain.com/api
# Bot Configuration
VITE_BOT_USERNAME=your_bot_username
# Development
VITE_DEV_MODE=true
# Start development server
npm run dev
# Open in browser
# http://localhost:3000
- Create Bot: Message @BotFather →
/newbot
- Set WebApp:
/newapp
→ Select your bot → Enter app details - Configure Domain: Add your domain to bot settings
- Test: Open your bot → Menu button → Your app
src/
├── 📁 components/ # Reusable UI components
│ ├── ui/ # Base UI components (Button, Card, etc.)
│ ├── TelegramProvider.tsx
│ ├── ThemeProvider.tsx
│ ├── BackButton.tsx
│ ├── MainButton.tsx
│ ├── HapticButton.tsx
│ ├── UserInfo.tsx
│ ├── FeatureCard.tsx
│ └── SensorDisplay.tsx
├── 📁 hooks/ # Custom React hooks
│ ├── useTelegram.ts # Main Telegram integration
│ ├── useBackButton.ts # Back button management
│ ├── useMainButton.ts # Main button control
│ ├── useHapticFeedback.ts # Haptic feedback
│ ├── useCloudStorage.ts # Cloud storage operations
│ ├── useBiometric.ts # Biometric authentication
│ ├── useQRScanner.ts # QR code scanning
│ ├── useClipboard.ts # Clipboard read access
│ ├── useWriteClipboard.ts # Clipboard write access
│ ├── useLocation.ts # Location services
│ ├── useGyroscope.ts # Gyroscope sensor
│ ├── useAccelerometer.ts # Accelerometer sensor
│ ├── useDeviceOrientation.ts # Device orientation
│ ├── useFullscreen.ts # Fullscreen mode
│ ├── useOrientation.ts # Orientation lock
│ ├── useHomeScreen.ts # Home screen installation
│ ├── useStorySharing.ts # Story sharing
│ ├── usePopups.ts # Alerts and popups
│ └── useTheme.ts # Theme management
├── 📁 pages/ # Page components
│ ├── Home.tsx # Main landing page
│ ├── Features.tsx # Feature demonstrations
│ ├── Sensors.tsx # Motion sensor testing
│ └── Documentation.tsx # API documentation
├── 📁 types/ # TypeScript definitions
│ └── telegram.d.ts # Complete Telegram WebApp types
├── 📁 utils/ # Utility functions
│ ├── telegram.ts # Telegram helper functions
│ └── api.ts # API client configuration
├── 📁 styles/ # CSS files
│ └── globals.css # Global styles and animations
├── App.tsx # Main app component
└── main.tsx # Entry point
Main hook for Telegram WebApp integration.
import { useTelegram } from '@/hooks/useTelegram'
function MyComponent() {
const { webApp, user, isReady, platform, version } = useTelegram()
if (!isReady) return <div>Loading...</div>
return (
<div>
<h1>Hello, {user?.first_name}!</h1>
<p>Platform: {platform}</p>
<p>Version: {version}</p>
</div>
)
}
Provides haptic feedback functionality.
import { useHapticFeedback } from '@/hooks/useHapticFeedback'
function HapticExample() {
const haptic = useHapticFeedback()
return (
<div>
<button onClick={() => haptic.impactLight()}>Light Impact</button>
<button onClick={() => haptic.impactMedium()}>Medium Impact</button>
<button onClick={() => haptic.impactHeavy()}>Heavy Impact</button>
<button onClick={() => haptic.notificationSuccess()}>Success</button>
<button onClick={() => haptic.notificationError()}>Error</button>
</div>
)
}
Manages the Telegram main button.
import { useMainButton } from '@/hooks/useMainButton'
function MainButtonExample() {
const { setText, show, hide, enable, disable } = useMainButton(
"Continue",
() => console.log("Main button clicked!"),
{ color: '#007AFF', is_active: true }
)
return (
<div>
<button onClick={() => setText("New Text")}>Change Text</button>
<button onClick={show}>Show</button>
<button onClick={hide}>Hide</button>
</div>
)
}
Handles the back button functionality.
import { useBackButton } from '@/hooks/useBackButton'
import { useNavigate } from 'react-router-dom'
function BackButtonExample() {
const navigate = useNavigate()
const { show, hide } = useBackButton(() => navigate(-1))
return (
<div>
<button onClick={show}>Show Back Button</button>
<button onClick={hide}>Hide Back Button</button>
</div>
)
}
Telegram cloud storage operations.
import { useCloudStorage } from '@/hooks/useCloudStorage'
function CloudStorageExample() {
const { setItem, getItem, removeItem, getKeys, loading } = useCloudStorage()
const saveData = async () => {
await setItem('user_preference', 'dark_mode')
console.log('Data saved!')
}
const loadData = async () => {
const preference = await getItem('user_preference')
console.log('User preference:', preference)
}
const getAllKeys = async () => {
const keys = await getKeys()
console.log('All keys:', keys)
}
return (
<div>
<button onClick={saveData} disabled={loading}>Save Data</button>
<button onClick={loadData} disabled={loading}>Load Data</button>
<button onClick={getAllKeys} disabled={loading}>Get All Keys</button>
</div>
)
}
Biometric authentication (fingerprint/face recognition).
import { useBiometric } from '@/hooks/useBiometric'
function BiometricExample() {
const {
requestAccess,
authenticate,
updateToken,
isAvailable,
isAccessGranted,
biometricType
} = useBiometric()
const handleAuth = async () => {
if (!isAvailable) {
console.log('Biometric not available')
return
}
const access = await requestAccess('Authentication required for secure access')
if (access) {
const result = await authenticate('Please authenticate to continue')
if (result.success) {
console.log('Authentication successful!', result.token)
}
}
}
return (
<div>
<p>Biometric Type: {biometricType}</p>
<p>Available: {isAvailable ? 'Yes' : 'No'}</p>
<p>Access Granted: {isAccessGranted ? 'Yes' : 'No'}</p>
<button onClick={handleAuth}>Authenticate</button>
</div>
)
}
QR code scanning functionality.
import { useQRScanner } from '@/hooks/useQRScanner'
function QRScannerExample() {
const { scanQR, closeScan, isScanning } = useQRScanner()
const handleScan = async () => {
const result = await scanQR('Scan any QR code')
if (result) {
console.log('Scanned:', result)
}
}
return (
<div>
<button onClick={handleScan} disabled={isScanning}>
{isScanning ? 'Scanning...' : 'Scan QR Code'}
</button>
{isScanning && (
<button onClick={closeScan}>Cancel Scan</button>
)}
</div>
)
}
GPS location services.
import { useLocation } from '@/hooks/useLocation'
function LocationExample() {
const { getLocation, requestLocation, location, loading } = useLocation()
const handleGetLocation = async () => {
const loc = await requestLocation()
if (loc) {
console.log(`Location: ${loc.latitude}, ${loc.longitude}`)
}
}
return (
<div>
<button onClick={handleGetLocation} disabled={loading}>
{loading ? 'Getting Location...' : 'Get Location'}
</button>
{location && (
<div>
<p>Latitude: {location.latitude}</p>
<p>Longitude: {location.longitude}</p>
{location.altitude && <p>Altitude: {location.altitude}m</p>}
</div>
)}
</div>
)
}
Read and write clipboard operations.
import { useClipboard } from '@/hooks/useClipboard'
import { useWriteClipboard } from '@/hooks/useWriteClipboard'
function ClipboardExample() {
const { readText, loading: readLoading } = useClipboard()
const { writeText, loading: writeLoading } = useWriteClipboard()
const handleRead = async () => {
const text = await readText()
console.log('Clipboard content:', text)
}
const handleWrite = async () => {
const success = await writeText('Hello from Telegram WebApp!')
console.log('Write success:', success)
}
return (
<div>
<button onClick={handleRead} disabled={readLoading}>
Read Clipboard
</button>
<button onClick={handleWrite} disabled={writeLoading}>
Write to Clipboard
</button>
</div>
)
}
Device rotation rate measurement.
import { useGyroscope } from '@/hooks/useGyroscope'
function GyroscopeExample() {
const { start, stop, data, isStarted, loading, isAvailable } = useGyroscope()
const handleToggle = async () => {
if (isStarted) {
await stop()
} else {
await start({ refresh_rate: 100 })
}
}
return (
<div>
<button onClick={handleToggle} disabled={!isAvailable || loading}>
{isStarted ? 'Stop' : 'Start'} Gyroscope
</button>
{isStarted && (
<div>
<p>X: {data.x.toFixed(2)} rad/s</p>
<p>Y: {data.y.toFixed(2)} rad/s</p>
<p>Z: {data.z.toFixed(2)} rad/s</p>
</div>
)}
</div>
)
}
Device acceleration measurement.
import { useAccelerometer } from '@/hooks/useAccelerometer'
function AccelerometerExample() {
const { start, stop, data, isStarted, loading, isAvailable } = useAccelerometer()
const handleToggle = async () => {
if (isStarted) {
await stop()
} else {
await start({ refresh_rate: 100 })
}
}
return (
<div>
<button onClick={handleToggle} disabled={!isAvailable || loading}>
{isStarted ? 'Stop' : 'Start'} Accelerometer
</button>
{isStarted && (
<div>
<p>X: {data.x.toFixed(2)} m/s²</p>
<p>Y: {data.y.toFixed(2)} m/s²</p>
<p>Z: {data.z.toFixed(2)} m/s²</p>
</div>
)}
</div>
)
}
Device orientation in 3D space.
import { useDeviceOrientation } from '@/hooks/useDeviceOrientation'
function OrientationExample() {
const { start, stop, data, isStarted, loading, isAvailable } = useDeviceOrientation()
const handleToggle = async () => {
if (isStarted) {
await stop()
} else {
await start({ refresh_rate: 100, need_absolute: true })
}
}
return (
<div>
<button onClick={handleToggle} disabled={!isAvailable || loading}>
{isStarted ? 'Stop' : 'Start'} Orientation
</button>
{isStarted && (
<div>
<p>Alpha: {data.alpha.toFixed(1)}°</p>
<p>Beta: {data.beta.toFixed(1)}°</p>
<p>Gamma: {data.gamma.toFixed(1)}°</p>
<p>Absolute: {data.absolute ? 'Yes' : 'No'}</p>
</div>
)}
</div>
)
}
Fullscreen mode control.
import { useFullscreen } from '@/hooks/useFullscreen'
function FullscreenExample() {
const { requestFullscreen, exitFullscreen, isFullscreen, isAvailable } = useFullscreen()
const handleToggle = () => {
if (isFullscreen) {
exitFullscreen()
} else {
requestFullscreen()
}
}
return (
<div>
<button onClick={handleToggle} disabled={!isAvailable}>
{isFullscreen ? 'Exit' : 'Enter'} Fullscreen
</button>
<p>Status: {isFullscreen ? 'Fullscreen' : 'Normal'}</p>
</div>
)
}
Device orientation lock.
import { useOrientation } from '@/hooks/useOrientation'
function OrientationLockExample() {
const { lockOrientation, unlockOrientation, orientation, isLocked, isAvailable } = useOrientation()
const handleToggle = () => {
if (isLocked) {
unlockOrientation()
} else {
lockOrientation()
}
}
return (
<div>
<button onClick={handleToggle} disabled={!isAvailable}>
{isLocked ? 'Unlock' : 'Lock'} Orientation
</button>
<p>Current: {orientation}</p>
<p>Status: {isLocked ? 'Locked' : 'Unlocked'}</p>
</div>
)
}
Add app to device home screen.
import { useHomeScreen } from '@/hooks/useHomeScreen'
function HomeScreenExample() {
const { addToHomeScreen, checkStatus, status, loading, isAvailable } = useHomeScreen()
const handleAdd = async () => {
await checkStatus()
if (status === 'missed') {
addToHomeScreen()
}
}
return (
<div>
<button onClick={handleAdd} disabled={!isAvailable || loading}>
Add to Home Screen
</button>
<p>Status: {status}</p>
</div>
)
}
Share content to Telegram Stories.
import { useStorySharing } from '@/hooks/useStorySharing'
function StoryExample() {
const { shareToStory, isAvailable } = useStorySharing()
const handleShare = () => {
shareToStory('https://picsum.photos/400/600', {
text: 'Check out this amazing app!',
widget_link: {
url: 'https://t.me/your_bot',
name: 'Open App'
}
})
}
return (
<button onClick={handleShare} disabled={!isAvailable}>
Share to Story
</button>
)
}
Native popup dialogs.
import { usePopups } from '@/hooks/usePopups'
function PopupExample() {
const { showAlert, showConfirm, showPopup } = usePopups()
const handleAlert = () => {
showAlert('This is an alert message!')
}
const handleConfirm = async () => {
const confirmed = await showConfirm('Are you sure you want to continue?')
console.log('User confirmed:', confirmed)
}
const handleCustomPopup = async () => {
const result = await showPopup({
title: 'Choose an option',
message: 'What would you like to do?',
buttons: [
{ id: 'option1', type: 'default', text: 'Option 1' },
{ id: 'option2', type: 'default', text: 'Option 2' },
{ id: 'cancel', type: 'cancel', text: 'Cancel' }
]
})
console.log('Selected option:', result)
}
return (
<div>
<button onClick={handleAlert}>Show Alert</button>
<button onClick={handleConfirm}>Show Confirm</button>
<button onClick={handleCustomPopup}>Show Custom Popup</button>
</div>
)
}
Versatile button component with multiple variants.
import { Button } from '@/components/ui/Button'
function ButtonExample() {
return (
<div className="space-x-2">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
<Button loading>Loading...</Button>
<Button disabled>Disabled</Button>
</div>
)
}
Flexible card component for content organization.
import { Card, CardHeader, CardContent, CardTitle, CardDescription } from '@/components/ui/Card'
function CardExample() {
return (
<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card description goes here</CardDescription>
</CardHeader>
<CardContent>
<p>Card content...</p>
</CardContent>
</Card>
)
}
Status and category indicators.
import { Badge } from '@/components/ui/Badge'
function BadgeExample() {
return (
<div className="space-x-2">
<Badge variant="default">Default</Badge>
<Badge variant="success">Success</Badge>
<Badge variant="warning">Warning</Badge>
<Badge variant="error">Error</Badge>
<Badge variant="info">Info</Badge>
</div>
)
}
Button with built-in haptic feedback.
import { HapticButton } from '@/components/HapticButton'
function HapticButtonExample() {
return (
<div className="space-y-2">
<HapticButton hapticType="light" onClick={() => console.log('Light haptic')}>
Light Haptic
</HapticButton>
<HapticButton hapticType="medium" onClick={() => console.log('Medium haptic')}>
Medium Haptic
</HapticButton>
<HapticButton hapticType="heavy" onClick={() => console.log('Heavy haptic')}>
Heavy Haptic
</HapticButton>
</div>
)
}
Specialized card for feature demonstrations.
import { FeatureCard } from '@/components/FeatureCard'
function FeatureCardExample() {
return (
<FeatureCard
title="Cloud Storage"
description="Store data in Telegram's cloud"
status="available"
onTest={() => console.log('Testing feature')}
icon={<CloudIcon />}
>
<p>Additional content goes here...</p>
</FeatureCard>
)
}
Display Telegram user information.
import { UserInfo } from '@/components/UserInfo'
function UserInfoExample() {
return <UserInfo />
}
-
Install Vercel CLI
npm i -g vercel
-
Login to Vercel
vercel login
-
Deploy
vercel --prod
-
Set Environment Variables
vercel env add VITE_API_BASE_URL vercel env add VITE_BOT_USERNAME
-
Build the project
npm run build
-
Deploy via Netlify CLI
npm install -g netlify-cli netlify deploy --prod --dir=dist
-
Or drag & drop the
dist
folder to Netlify
-
Install gh-pages
npm install --save-dev gh-pages
-
Add deploy script to package.json
{ "scripts": { "deploy": "gh-pages -d dist" } }
-
Deploy
npm run build npm run deploy
- Connect GitHub repository to Railway
- Set environment variables in Railway dashboard
- Deploy automatically on git push
Variable | Description | Required | Default |
---|---|---|---|
VITE_API_BASE_URL |
Backend API base URL | No | /api |
VITE_BOT_USERNAME |
Telegram bot username | No | - |
VITE_DEV_MODE |
Development mode flag | No | false |
-
Create Bot
/newbot → Follow instructions
-
Set Menu Button
/setmenubutton → Select bot → Send web app URL
-
Configure Domain
/setdomain → Select bot → Add your domain
-
Set Commands (Optional)
/setcommands → Select bot → Add commands: start - Start the bot help - Get help app - Open Mini App
# Start development server
npm run dev
# Test in browser
open http://localhost:3000
-
Use ngrok for local testing
npm install -g ngrok ngrok http 3000
-
Update bot WebApp URL with ngrok URL
-
Test in Telegram
- Open your bot
- Click menu button
- Test all features
- Deploy to staging environment
- Update bot with staging URL
- Test all features thoroughly
- Deploy to production
// Backend validation example
import crypto from 'crypto'
function validateTelegramWebAppData(initData: string, botToken: string): boolean {
const urlParams = new URLSearchParams(initData)
const hash = urlParams.get('hash')
urlParams.delete('hash')
const dataCheckString = Array.from(urlParams.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
}
import DOMPurify from 'dompurify'
function sanitizeInput(input: string): string {
return DOMPurify.sanitize(input)
}
// Example rate limiting middleware
const rateLimit = require('express-rate-limit')
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
})
app.use('/api/', limiter)
// Redirect HTTP to HTTPS
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`)
} else {
next()
}
})
# Check console for errors
# Ensure HTTPS is enabled
# Verify bot configuration
# Check domain whitelist
// Check Telegram version
const { webApp } = useTelegram()
if (webApp?.isVersionAtLeast('6.4')) {
// Feature available
} else {
// Feature not supported
}
// Ensure ThemeProvider is wrapping your app
<TelegramProvider>
<ThemeProvider>
<App />
</ThemeProvider>
</TelegramProvider>
// Check if running in Telegram
import { isTelegramWebApp } from '@/utils/telegram'
if (isTelegramWebApp()) {
haptic.impactMedium()
} else {
console.log('Haptic feedback only works in Telegram')
}
// Enable debug logging
localStorage.setItem('telegram-debug', 'true')
// Check WebApp availability
console.log('Telegram WebApp:', window.Telegram?.WebApp)
console.log('User:', window.Telegram?.WebApp?.initDataUnsafe?.user)
We welcome contributions! Please follow these steps:
git clone https://github.com/vishal-1756/telegram-miniapp-boilerplate.git
cd telegram-miniapp-boilerplate
git checkout -b feature/amazing-feature
- Follow existing code style
- Add TypeScript types
- Include tests if applicable
- Update documentation
git commit -m "feat: add amazing feature"
git push origin feature/amazing-feature
- TypeScript: Strict mode enabled
- ESLint: Follow provided configuration
- Prettier: Auto-formatting enabled
- Naming: Use camelCase for variables, PascalCase for components
This project is licensed under the MIT License - see the LICENSE file for details.
- Email: kora3244@gmail.com
- Telegram: @Darkee0_0
- Complete Telegram WebApp API coverage
- Motion sensors integration
- Modern UI components
- TypeScript definitions
- Comprehensive documentation
If this project helped you, please consider:
- ⭐ Starring the repository
- 🐛 Reporting bugs and issues
- 💡 Suggesting new features
- 🤝 Contributing to the codebase
- 📢 Sharing with the community
Built with ❤️ for the Telegram Mini App ecosystem