Module 6: LIFF — LINE Front-end Framework

Full web apps that run inside LINE — the bridge between your OA and rich interactive experiences.

⏱ 30 min read 📖 Advanced

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:

SizeHeightBest for
Compact~50% of screenQuick actions, forms, confirmations
Tall~85% of screenProduct listings, content browsing
Full100% of screenFull 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

  1. Go to LINE Developers Console
  2. Select your Messaging API channel
  3. Go to the LIFF tab
  4. Click Add LIFF App
  5. Fill in:
    • LIFF app name — display name
    • Endpoint URL — your web app URL (HTTPS required)
    • Size — Compact, Tall, or Full
    • Scopesprofile, chat_message.write, openid
    • Bot link featureNormal or Aggressive
  6. 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

APIDescriptionRequires 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 profileprofile
liff.getIDToken()Get ID token (decoded JWT)openid
liff.getAccessToken()Get LINE access token
liff.getDecodedIDToken()Get decoded ID token payloadopenid
liff.sendMessages()Send message(s) to OA chat (up to 5)chat_message.write
liff.shareTargetPicker()Share message to friends/groupschat_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.permanentLinkCreate 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.