Module 6: LIFF — LINE Front-end Framework
Full web apps that run inside LINE — the bridge between your OA and rich interactive experiences.
6.1 What is LIFF?
LIFF (LINE Front-end Framework) lets you build web applications that run inside the LINE app. When a user taps a LIFF URL, it opens a web view within LINE — giving you a full web experience with access to LINE-specific APIs.
What LIFF can do:
- Get the user's LINE profile (name, picture, user ID)
- Send messages on behalf of the user
- Share messages with friends via the share target picker
- Scan QR codes / barcodes
- Check if user follows your OA
- Prompt users to add your OA as friend
- Open links in external browser
LIFF apps are regular HTML/JS/CSS web apps. They're hosted on your server and loaded inside a WKWebView (iOS) or WebView (Android) within LINE.
6.2 LIFF Screen Sizes
When registering a LIFF app, you choose a size:
| Size | Height | Best for |
|---|---|---|
| Compact | ~50% of screen | Quick actions, forms, confirmations |
| Tall | ~85% of screen | Product listings, content browsing |
| Full | 100% of screen | Full web apps, checkout flows |
Users can drag the LIFF window to resize (Compact/Tall) or it fills the full screen (Full).
6.3 Setting Up a LIFF App
Step 1: Add LIFF to your channel
- Go to LINE Developers Console
- Select your Messaging API channel
- Go to the LIFF tab
- Click Add LIFF App
- Fill in:
- LIFF app name — display name
- Endpoint URL — your web app URL (HTTPS required)
- Size — Compact, Tall, or Full
- Scopes —
profile,chat_message.write,openid - Bot link feature —
NormalorAggressive
- Copy the LIFF ID — you'll use this to initialize the LIFF SDK
Step 2: Initialize LIFF in your web app
<!-- In your HTML -->
<script src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>
// Initialize LIFF
await liff.init({ liffId: 'YOUR_LIFF_ID' });
// Check if user is logged in
if (!liff.isLoggedIn()) {
// Don't call getProfile yet — you need to log in first
liff.login(); // redirects to LINE login
}
// Get user profile
const profile = await liff.getProfile();
console.log(profile.userId, profile.displayName, profile.pictureUrl);
// Get ID token (for your backend to verify)
const idToken = liff.getIDToken();
6.4 LIFF API Reference
| API | Description | Requires scope |
|---|---|---|
liff.init() | Initialize LIFF SDK | — |
liff.isLoggedIn() | Check if user has LINE login session | — |
liff.login() | Redirect user to LINE login | — |
liff.logout() | Log user out | — |
liff.getProfile() | Get user's LINE profile | profile |
liff.getIDToken() | Get ID token (decoded JWT) | openid |
liff.getAccessToken() | Get LINE access token | — |
liff.getDecodedIDToken() | Get decoded ID token payload | openid |
liff.sendMessages() | Send message(s) to OA chat (up to 5) | chat_message.write |
liff.shareTargetPicker() | Share message to friends/groups | chat_message.write |
liff.scanCodeV2() | Open QR/barcode scanner | — |
liff.getFriendship() | Check if user follows linked OA | — |
liff.requestFriendship() | Prompt user to add OA as friend | — |
liff.openWindow() | Open URL in internal/external browser | — |
liff.closeWindow() | Close LIFF app | — |
liff.getContext() | Get current context (type, userId, groupId, etc.) | — |
liff.getOS() | Get OS: ios, android, web | — |
liff.getLanguage() | Get LINE language setting | — |
liff.getVersion() | Get LIFF SDK version | — |
liff.isInClient() | Check if running inside LINE app | — |
liff.permanentLink | Create permanent LIFF URLs | — |
6.5 Practical Example: Complete LIFF App
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My LIFF App</title>
<script src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>
<style>
body { font-family: sans-serif; padding: 16px; margin: 0; }
.profile { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
.profile img { width: 48px; height: 48px; border-radius: 50%; }
.btn { background: #06c755; color: white; border: none; padding: 12px 24px;
border-radius: 8px; font-size: 16px; cursor: pointer; width: 100%; margin: 6px 0; }
.btn:hover { opacity: 0.9; }
</style>
</head>
<body>
<div id="app">
<div class="profile">
<img id="profilePic" src="" alt="">
<div><strong id="profileName"></strong><br><small id="profileId"></small></div>
</div>
<button class="btn" onclick="sendMessage()">Send Message to OA</button>
<button class="btn" onclick="shareWithFriend()">Share with Friends</button>
<button class="btn" onclick="scanQR()">Scan QR Code</button>
</div>
<script>
async function main() {
await liff.init({ liffId: 'YOUR_LIFF_ID' });
if (!liff.isLoggedIn()) {
liff.login();
return;
}
const profile = await liff.getProfile();
document.getElementById('profilePic').src = profile.pictureUrl;
document.getElementById('profileName').textContent = profile.displayName;
document.getElementById('profileId').textContent = 'ID: ' + profile.userId;
}
async function sendMessage() {
await liff.sendMessages([
{ type: 'text', text: 'Hello from LIFF!' },
{ type: 'sticker', packageId: '11537', stickerId: '52002734' }
]);
alert('Message sent to OA!');
}
async function shareWithFriend() {
await liff.shareTargetPicker([
{ type: 'text', text: 'Check out this cool app!' }
]);
}
async function scanQR() {
const result = await liff.scanCodeV2();
alert('Scanned: ' + result.value);
}
main().catch(err => console.error(err));
</script>
</body>
</html>
HTTPS required: LIFF endpoints MUST be served over HTTPS. Self-signed certs won't work. Use Vercel, Netlify, Railway, or your own server with Let's Encrypt.
6.6 LIFF + Your Backend: User Authentication Flow
LIFF provides the ID token (liff.getIDToken()), which is a signed JWT. Your backend verifies this token to authenticate the user.
// Frontend (LIFF)
const idToken = liff.getIDToken();
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idToken })
});
const data = await response.json();
// Backend (Node.js)
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: 'https://api.line.me/oauth2/v2.1/keys'
});
function verifyIdToken(idToken) {
return new Promise((resolve, reject) => {
const header = jwt.decode(idToken, { complete: true }).header;
client.getSigningKey(header.kid, (err, key) => {
if (err) return reject(err);
const signingKey = key.getPublicKey();
jwt.verify(idToken, signingKey, {
issuer: 'https://access.line.me',
audience: process.env.CHANNEL_ID
}, (err, decoded) => {
if (err) return reject(err);
resolve(decoded); // Contains sub (userId), name, picture, email
});
});
});
}
The decoded ID Token contains:
{
"iss": "https://access.line.me",
"sub": "U4a8b...", // LINE User ID
"aud": "165...", // Your Channel ID
"exp": 1690000000,
"iat": 1689996400,
"name": "John Doe",
"picture": "https://profile.line-scdn.net/..."
}
This is the key integration point: use LIFF to get the user's identity, then link it with your own database. You now know exactly who the LINE user is in your system.
6.7 LIFF Use Cases for Thailand
- E-commerce catalog: Product browsing LIFF with add-to-cart flow
- Restaurant ordering: Menu browsing + order submission + payment via PromptPay
- Booking system: Appointment/reservation calendar LIFF
- Customer portal: View order history, loyalty points, membership card
- Lead generation: Form with LIFF + sendMessages to confirm submission
- Contest/voting: Interactive experience with shareTargetPicker for viral sharing
🇪🇩 Thailand Use Case
Many Thai brands use LIFF for e-commerce catalogs with LINE's built-in payment. The LIFF shows products, user adds to cart, then completes payment via MyShop or custom integration with PromptPay/Omise/2C2P.