สอนพัฒนา LIFF App 2026 ด้วย Next.js และ TypeScript
วิธีสร้าง LIFF (LINE Front-end Framework) app ในปี 2026 ตั้งแต่ตั้งค่า channel และ endpoint URL กฎของ liff.init การยืนยัน ID token ฝั่งเซิร์ฟเวอร์ sendMessages shareTargetPicker deep link การทดสอบด้วย LIFF CLI และอัปเดตความปลอดภัย v2.31.1

#LIFF คืออะไร?
LIFF (LINE Front-end Framework) คือแพลตฟอร์มของ LY Corporation สำหรับเว็บแอปที่เปิดใช้งานภายใน LINE ตัว LIFF app ก็คือหน้าเว็บ HTTPS ธรรมดาที่ใส่ LIFF SDK เข้าไป เมื่อผู้ใช้เปิดผ่าน LIFF URL ใน LINE แอปจะรู้ตัวตน LINE ของผู้ใช้ได้โดยไม่ต้องมีฟอร์มล็อกอิน ส่งข้อความเข้าแชทที่เปิดแอปมาได้ และเปิดหน้าเลือกผู้รับ (share target picker) ของ LINE ได้ บทความนี้พาสร้าง LIFF app ด้วย Next.js และ TypeScript โดยทุกกฎตรวจสอบกับเอกสาร LINE Developers ณ เดือนตุลาคม 2026
#LIFF app เปิดได้ที่ไหนบ้าง
| สภาพแวดล้อม | ผู้ใช้เข้ามาทางไหน | ใช้อะไรได้ |
|---|---|---|
| LIFF browser | แตะ LIFF URL (https://liff.line.me/{liffId}) ใน LINE | ใช้ได้ครบ: ล็อกอินอัตโนมัติ, กำหนดขนาดหน้าจอ, sendMessages, share target picker, ตัวสแกนโค้ด 2 มิติ, การเปลี่ยนจาก LIFF ไป LIFF |
| In-app browser ของ LINE | แตะ endpoint URL ตรง ๆ ในแชท | ถูกนับเป็น external browser: กำหนดขนาดหน้าจอไม่ได้, ไม่มี sendMessages, ไม่มี share target picker |
| External browser (Chrome, Safari, Edge, Firefox) | เปิด URL นอก LINE | ล็อกอิน LINE ผ่าน liff.login() ฟีเจอร์เฉพาะของ LINE ใช้ได้จำกัด |
liff.isInClient() จะคืนค่า true เฉพาะใน LIFF browser เท่านั้น จึงควรแชร์ LIFF URL เสมอ ไม่ใช่ endpoint URL
#เลือก LIFF หรือ LINE MINI App?
LINE MINI App ใช้ LIFF SDK ตัวเดียวกัน แต่สร้างอยู่ใน LINE MINI App channel และจะได้ฟีเจอร์เพิ่มเมื่อผ่านการตรวจสอบ (verification review) ของ LINE เช่น เครื่องหมายยืนยัน, custom path, ทางลัดบนหน้าจอโฮม และ channel consent simplification ทาง LINE ประกาศว่าจะรวม LIFF เข้ากับ LINE MINI App และแนะนำให้สร้าง LIFF app ใหม่เป็น LINE MINI App (ข่าววันที่ 12 กุมภาพันธ์ 2025) และตั้งแต่วันที่ 11 มีนาคม 2026 ผู้ที่ได้รับอนุญาตตาม LINE MINI App Policy ทุกคนสามารถเผยแพร่ MINI App แบบยังไม่ผ่านการยืนยัน (unverified) ในไทยและไต้หวันได้ ส่วนการยื่นขอยืนยันในสองประเทศนี้ทำได้เฉพาะ channel ภายใต้ provider ที่ได้รับการรับรองเท่านั้น (ข่าว LINE Developers ปี 2026) เนื้อหาทั้งหมดด้านล่างใช้ได้กับทั้งสองแบบ
#LIFF ทำอะไรได้ และทำอะไรไม่ได้
- ทำได้: อ่านโปรไฟล์และ ID token ของผู้ใช้, ส่งข้อความเข้าแชทปัจจุบันได้ครั้งละไม่เกิน 5 ข้อความ, แชร์ข้อความให้เพื่อนและกลุ่ม, สแกนโค้ด 2 มิติ, ตรวจสอบและชวนให้เพิ่ม LINE Official Account เป็นเพื่อน, สร้าง permanent link
- ทำไม่ได้: แทนที่ backend ของคุณ บอท push message และ broadcast ยังต้องใช้ Messaging API นอกจากนี้ LIFF browser ยังไม่รองรับ service worker, แอตทริบิวต์
download, meta tag theme-color และการเพิ่มลงหน้าจอโฮม (ความต่างกับ external browser)
ถ้าต้องการให้ทีมพัฒนาแอปให้ บริการพัฒนาแอปบน LINE ของเรารับงาน LIFF และ LINE MINI App แบบพัฒนาตามความต้องการ (custom development)
#ตั้งค่า Channel และเพิ่ม LIFF App
#1. เลือก channel ให้ถูกประเภท
LIFF app ต้องอยู่ใน LINE Login channel หรือ LINE MINI App channel เท่านั้น เพิ่ม LIFF app ใหม่ลงใน Messaging API channel ไม่ได้แล้ว (ยกเลิกตั้งแต่ 5 กุมภาพันธ์ 2020) ถ้าแอปต้องทำงานร่วมกับ LINE Official Account ให้สร้าง LINE Login channel ภายใต้ provider เดียวกับ Messaging API channel และเชื่อม LINE OA เข้ากับ channel เพื่อให้ตัวเลือกเพิ่มเพื่อน (add friend option) และเมธอดเรื่องสถานะเพื่อนใช้งานได้
#2. เพิ่ม LIFF app
ใน LINE Developers Console เปิด channel ไปที่แท็บ LIFF แล้วกด Add หนึ่ง channel มี LIFF app ได้สูงสุด 30 แอป (วิธีเพิ่ม LIFF app)
| การตั้งค่า | ต้องใส่อะไร |
|---|---|
| LIFF app name | ชื่อแอป ห้ามมีคำว่า "LINE" หรือคำที่คล้ายกัน |
| Size | Compact, Tall หรือ Full คือความสูงของหน้าจอ LIFF browser โดย liff.requestFriendship() และ module mode ต้องใช้ Full |
| Endpoint URL | URL ของแอป ต้องเป็น https และใส่ URL fragment (#...) ไม่ได้ |
| Scopes | openid (ID token), email (เฉพาะ channel ที่ได้สิทธิ์อีเมล), profile (getProfile, getFriendship), chat_message.write (sendMessages) |
| Add friend option | Off, On (normal) หรือ On (aggressive) เพื่อชวนเพิ่ม LINE OA เป็นเพื่อนในหน้าขอความยินยอม |
| Scan QR | เปิดเมื่อจะใช้ liff.scanCodeV2() |
| Module mode | ซ่อนปุ่ม action ที่ header (ใช้ได้กับขนาด Full เท่านั้น) |
เมื่อบันทึกจะได้ LIFF ID (เช่น 1234567890-AbcdEfgh) และ LIFF URL (https://liff.line.me/1234567890-AbcdEfgh)
#3. สร้างโปรเจกต์
# สร้างโปรเจกต์ใหม่ด้วยเครื่องมือของ LINE (มีเทมเพลต Next.js ให้เลือก)
npx @line/create-liff-app
# หรือเพิ่ม SDK ลงในแอป Next.js ที่มีอยู่แล้ว
npm install @line/liff@^2.31.2
# .env.local
NEXT_PUBLIC_LIFF_ID=1234567890-AbcdEfgh
LINE_LOGIN_CHANNEL_ID=1234567890
อัปเดตความปลอดภัย 30 กันยายน 2026: LINE ประกาศเลิกสนับสนุน (deprecated) LIFF SDK v2.20.0 ถึง v2.31.0 เพราะพบปัญหาที่อาจกระทบความปลอดภัย และขอให้ทุกคนอัปเดตเป็น v2.31.1 ขึ้นไป แอปที่โหลด
https://static.line-scdn.net/liff/edge/2/sdk.jsได้รับการอัปเดตอัตโนมัติ แต่ถ้าติดตั้งผ่าน npm หรือระบุเวอร์ชันตายตัวใน CDN (.../liff/edge/versions/2.x.y/sdk.js) ต้องอัปเดตเอง เวอร์ชันล่าสุดคือ v2.31.2 (7 ตุลาคม 2026) ดูรายละเอียดใน LIFF release notes
#เริ่มต้น LIFF และยืนยันตัวผู้ใช้
#กฎของ liff.init()
สรุปจาก Developing a LIFF app:
- เรียก
liff.init()ทุกครั้งที่เปิดหน้าเว็บ รวมถึงหน้าอื่นภายในแอปเดียวกัน - เรียกบน endpoint URL หรือ URL ที่อยู่ใต้ endpoint URL เท่านั้น ถ้า endpoint คือ
https://example.com/app/การ init บนhttps://example.com/ไม่รับประกันว่าจะทำงาน (ตั้งแต่ LIFF v2.27.2 จะมีคำเตือนใน console) - อย่าเปลี่ยน URL (redirect ของ router,
history.replaceState, สคริปต์ที่แก้ URL) ก่อนที่ promise จะ resolve เพราะ LIFF ส่งliff.stateและข้อมูลยืนยันตัวตนมาใน query string - ใน external browser ผู้ใช้ยังไม่ได้ล็อกอิน ให้ใส่
withLoginOnExternalBrowser: trueหรือเรียกliff.login()เอง และอย่าส่ง authorization request ของ LINE Login จาก LIFF app
#LiffProvider สำหรับ Next.js App Router
SDK ต้องใช้ window จึงต้อง import เฉพาะฝั่งเบราว์เซอร์ภายใน useEffect:
// components/liff-provider.tsx
"use client"
import { createContext, useContext, useEffect, useState, type ReactNode } from "react"
import type { Liff } from "@line/liff"
type Profile = Awaited<ReturnType<Liff["getProfile"]>>
type LiffState = {
liff: Liff | null
profile: Profile | null
error: string | null
ready: boolean
}
const initialState: LiffState = { liff: null, profile: null, error: null, ready: false }
const LiffContext = createContext<LiffState>(initialState)
// init ครั้งเดียวต่อการโหลดหน้า แม้ React จะรัน effect สองครั้งในโหมด development
let liffPromise: Promise<Liff> | null = null
function loadLiff(liffId: string): Promise<Liff> {
liffPromise ??= import("@line/liff").then(async ({ default: liff }) => {
await liff.init({ liffId, withLoginOnExternalBrowser: true })
return liff
})
return liffPromise
}
export function LiffProvider({ liffId, children }: { liffId: string; children: ReactNode }) {
const [state, setState] = useState<LiffState>(initialState)
useEffect(() => {
let cancelled = false
loadLiff(liffId)
.then(async (liff) => {
const profile = liff.isLoggedIn() ? await liff.getProfile() : null
if (!cancelled) setState({ liff, profile, error: null, ready: true })
})
.catch((err: unknown) => {
const message = err instanceof Error ? err.message : String(err)
if (!cancelled) setState({ ...initialState, error: message, ready: true })
})
return () => {
cancelled = true
}
}, [liffId])
return <LiffContext.Provider value={state}>{children}</LiffContext.Provider>
}
export const useLiff = () => useContext(LiffContext)
ครอบ route ที่ต้องใช้ LIFF:
// app/(liff)/layout.tsx
import type { ReactNode } from "react"
import { LiffProvider } from "@/components/liff-provider"
export default function LiffLayout({ children }: { children: ReactNode }) {
return <LiffProvider liffId={process.env.NEXT_PUBLIC_LIFF_ID!}>{children}</LiffProvider>
}
#แสดงโปรไฟล์
// components/profile-card.tsx
"use client"
import { useLiff } from "./liff-provider"
export function ProfileCard() {
const { ready, error, profile } = useLiff()
if (!ready) return <p>กำลังโหลด…</p>
if (error) return <p>LIFF ผิดพลาด: {error}</p>
if (!profile) return null
return (
<div className="flex items-center gap-3">
{profile.pictureUrl && (
<img src={profile.pictureUrl} alt="" width={48} height={48} className="rounded-full" />
)}
<p className="font-semibold">{profile.displayName}</p>
</div>
)
}
liff.getProfile() ต้องใช้ scope profile และจะคืน userId กับ displayName รวมถึง pictureUrl และ statusMessage ถ้าผู้ใช้ตั้งไว้ โดย userId แยกตาม provider คนเดียวกันจะมี user ID คนละค่าเมื่ออยู่ใต้ provider อื่น
#ยืนยันตัวผู้ใช้ฝั่งเซิร์ฟเวอร์
ห้ามส่งโปรไฟล์ หรือ user ID ที่อ่านจากเบราว์เซอร์ ไปยัง API ของคุณ เพราะใครก็ปลอมได้ ให้ส่ง ID token หรือ access token แล้วให้เซิร์ฟเวอร์ตรวจสอบกับ LINE (Using user data in LIFF apps and servers)
// lib/verify-user.ts (ฝั่งเบราว์เซอร์)
import type { Liff } from "@line/liff"
// ส่ง ID token ไม่ใช่โปรไฟล์ แล้วให้เซิร์ฟเวอร์ตรวจกับ LINE
export async function getVerifiedUserId(liff: Liff): Promise<string> {
const idToken = liff.getIDToken() // ต้องมี scope openid
if (!idToken) throw new Error("ไม่มี ID token: ตรวจ scope openid และสถานะล็อกอิน")
const res = await fetch("/api/liff/verify", {
method: "POST",
headers: { Authorization: `Bearer ${idToken}` },
})
if (!res.ok) throw new Error(`ยืนยันไม่สำเร็จ: ${res.status}`)
const { userId } = (await res.json()) as { userId: string }
return userId
}
// app/api/liff/verify/route.ts
export async function POST(request: Request) {
const idToken = request.headers.get("authorization")?.replace(/^Bearer /, "")
if (!idToken) return Response.json({ error: "Missing ID token" }, { status: 401 })
const res = await fetch("https://api.line.me/oauth2/v2.1/verify", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
id_token: idToken,
// Channel ID ของ LINE Login channel ที่มี LIFF app นี้อยู่
client_id: process.env.LINE_LOGIN_CHANNEL_ID ?? "",
}),
})
if (!res.ok) return Response.json({ error: "Invalid ID token" }, { status: 401 })
const payload = (await res.json()) as { sub: string; name?: string; exp: number }
// payload.sub คือ LINE user ID ที่ผ่านการยืนยันแล้ว ใช้ค้นหาหรือสร้างผู้ใช้ในระบบ
return Response.json({ userId: payload.sub })
}
Verify endpoint จะปฏิเสธ token ที่รูปแบบผิด หมดอายุ ออกโดยเว็บอื่น หรือออกให้ channel อื่น (client_id) และคืน payload กลับมา โดย sub คือ user ID ถ้าอยากใช้ access token จาก liff.getAccessToken() แทน ให้เรียก GET https://api.line.me/oauth2/v2.1/verify?access_token=... ตรวจว่า client_id ตรงกับ channel ID ของคุณและ expires_in มากกว่าศูนย์ จากนั้นเรียก GET https://api.line.me/v2/profile ด้วย token นั้น และ access token จะถูกเพิกถอนเมื่อผู้ใช้ปิด LIFF app
#ส่งข้อความ แชร์ และชวนเพิ่มเพื่อน
#ส่งข้อความเข้าแชทปัจจุบัน
liff.sendMessages() ส่งข้อความได้ไม่เกิน 5 ข้อความ ในนามของผู้ใช้ เข้าไปในแชทที่เปิด LIFF app มา ใช้ได้เฉพาะเมื่อ:
- แอปเปิดอยู่ใน LIFF browser และเปิดมาจากแชท 1:1 แชทกลุ่ม หรือแชทหลายคน
- เปิด scope
chat_message.writeไว้และผู้ใช้อนุญาตแล้ว - แอปไม่ได้ถูกโหลดใหม่จาก "บริการที่ใช้ล่าสุด" (recently used services)
ถ้าไม่ครบเงื่อนไข promise จะ reject พร้อม error 403 ("user doesn't grant required permissions yet") การเปิดแอปจาก Keep Memo หรือผ่านการ redirect จากเว็บไซต์ก็ใช้ไม่ได้เช่นกัน
import type { Liff } from "@line/liff"
export async function sendToChat(liff: Liff, text: string) {
if (!liff.isInClient()) {
alert("กรุณาเปิดหน้านี้จากแชท LINE เพื่อส่งข้อความ")
return
}
try {
await liff.sendMessages([{ type: "text", text }])
} catch (err) {
// 403: ไม่ได้เปิดจากแชท, ผู้ใช้ไม่ได้อนุญาต chat_message.write
// หรือแอปถูกโหลดใหม่จาก "บริการที่ใช้ล่าสุด"
console.error("sendMessages failed", err)
}
}
#แชร์ด้วย share target picker
Share target picker ให้ผู้ใช้เลือกเพื่อน กลุ่ม และแชท (ไม่รวม OpenChat) แล้วส่งข้อความของคุณจากบัญชีของเขาเอง ต้องเปิดใช้ก่อน: ที่แท็บ LIFF กด shareTargetPicker ยอมรับ "Agreement Regarding Use of Information" แล้วกด Enable
export async function shareProduct(liff: Liff) {
if (!liff.isApiAvailable("shareTargetPicker")) return
const result = await liff.shareTargetPicker(
[
{
type: "flex",
altText: "ข้าวไก่ย่าง 129 บาท",
contents: {
type: "bubble",
hero: {
type: "image",
url: "https://example.com/images/chicken-set.jpg",
size: "full",
aspectRatio: "20:13",
aspectMode: "cover",
},
body: {
type: "box",
layout: "vertical",
contents: [
{ type: "text", text: "ข้าวไก่ย่าง", weight: "bold", size: "lg" },
{ type: "text", text: "129 บาท", color: "#06C755" },
],
},
footer: {
type: "box",
layout: "vertical",
contents: [
{
type: "button",
style: "primary",
color: "#06C755",
// ข้อความที่ส่งผ่าน share target picker ใช้ได้เฉพาะ URI action
action: { type: "uri", label: "สั่งเลย", uri: "https://liff.line.me/1234567890-AbcdEfgh/menu/42" },
},
],
},
},
},
],
{ isMultiple: true },
)
if (result) console.log(`[${result.status}] แชร์แล้ว`)
else console.log("ผู้ใช้ปิดหน้าเลือกผู้รับ")
}
สิ่งที่เอกสารอ้างอิงระบุไว้:
- ส่งได้ไม่เกิน 5 ข้อความ Flex Message และ template message ใช้ได้เฉพาะ URI action
- promise จะ resolve เป็น
{ status: "success" }เมื่อส่งสำเร็จ และเป็นค่า falsy เมื่อผู้ใช้ปิดหน้าเลือก ส่วนจะ reject เมื่อเกิดข้อผิดพลาดก่อนส่ง - ใน external browser บนมือถือ picker ต้องมี session single sign-on ของ LINE ผู้ใช้จึงอาจเห็นหน้าล็อกอินด้วยอีเมล
- LINE ไม่บอกว่ามีกี่คนที่ได้รับข้อความที่แชร์
isMultiple: falseจำกัดให้เลือกเพื่อนได้ 1 คนต่อครั้ง แต่ไม่ได้กันผู้ใช้แชร์ซ้ำ
#สถานะเพื่อนและตัวสแกนโค้ด 2 มิติ
export async function askToAddFriend(liff: Liff) {
const { friendFlag } = await liff.getFriendship() // ต้องมี scope profile
if (!friendFlag) await liff.requestFriendship() // LIFF v2.28.0 ขึ้นไป ใช้ได้กับขนาด Full เท่านั้น
}
export async function scanTableQr(liff: Liff) {
const { value } = await liff.scanCodeV2() // เปิด "Scan QR" ใน console ก่อน
return value
}
liff.getFriendship() ตรวจสถานะกับ LINE OA ที่เชื่อมไว้กับ channel ส่วน liff.requestFriendship() (v2.28.0, มีนาคม 2026) เปิดหน้าต่างย่อยชวนให้ผู้ใช้เพิ่มเพื่อนหรือเลิกบล็อกบัญชีนั้น ค่าที่คืนกลับมาไม่ได้บอกว่าผู้ใช้กดอะไร จึงต้องเรียก getFriendship() อีกครั้งหลังจากนั้น และ liff.scanCodeV2() ใช้ได้ใน LIFF browser บน iOS 14.3 ขึ้นไปและ Android รวมถึง external browser ที่รองรับ WebRTC
#แนวทางปฏิบัติที่ดี
#รองรับทั้งสามเบราว์เซอร์
// components/app-shell.tsx
"use client"
import type { ReactNode } from "react"
import { useLiff } from "./liff-provider"
export function AppShell({ children }: { children: ReactNode }) {
const { liff, ready } = useLiff()
if (!ready || !liff) return <p>กำลังโหลด…</p>
// ใน external browser ตัวเลือก withLoginOnExternalBrowser พาผู้ใช้ไปล็อกอิน LINE แล้ว
// ตรงนี้แค่ซ่อนฟีเจอร์ที่ต้องใช้ LIFF browser
const inLine = liff.isInClient()
return (
<main style={{ paddingBottom: "env(safe-area-inset-bottom)" }}>
{children}
{!inLine && <p>เปิดหน้านี้ใน LINE เพื่อแชร์หรือส่งเข้าแชท</p>}
</main>
)
}
ตรวจ liff.isApiAvailable("shareTargetPicker") ก่อนเรียก picker ทุกครั้ง ส่วน liff.getContext() บอกได้ว่าแอปถูกเปิดจากที่ไหน: utou (แชท 1:1), group, room, external หรือ none
#เปิดลิงก์ให้ถูกวิธี
liff.openWindow({ url, external }) เปิด URL ใน in-app browser ของ LINE (external: false ค่าเริ่มต้น) หรือนอก LINE ตั้งแต่ LINE 15.20.0 URL ที่รองรับ Universal Links หรือ App Links จะเปิดใน in-app browser ของ LINE เว้นแต่ส่ง external: true ซึ่งจะเปิดแอปที่เกี่ยวข้อง ขณะที่ LINE 14.20.0 ถึง 15.19.x จะสลับไปแอปเสมอ (liff.openWindow()) จึงไม่ควรพึ่งพฤติกรรมแบบใดแบบหนึ่ง
#Routing, deep link และการเปลี่ยนจาก LIFF ไป LIFF
- ทำ single-page app ด้วย History API เพราะ LIFF รองรับการ routing ด้วย fragment (
#) ได้จำกัด - LIFF URL ใส่ path และ query ได้ เช่น
https://liff.line.me/1234567890-AbcdEfgh/menu/42?table=7จะพาไปที่{endpoint URL}/menu/42?table=7หลังliff.init()ใช้ลิงก์แบบนี้ใน Rich Menu ปุ่มของ Flex Message และ QR code ได้ ส่วนliff.permanentLink.createUrlBy()สร้างลิงก์ของหน้าปัจจุบันให้:
export async function shareCurrentPage(liff: Liff) {
// https://liff.line.me/{liffId}/{path} จะเปิด path เดียวกันใต้ endpoint URL ของคุณ
const url = await liff.permanentLink.createUrlBy(window.location.href)
await navigator.clipboard.writeText(url)
}
- การเปิด LIFF app อื่นจาก LIFF app (LIFF-to-LIFF) ทำได้เฉพาะใน LIFF browser และหลังเปลี่ยนแอป scope
chat_message.writeอาจถูกปิด - ตั้งแต่ LIFF v2.31.2 (7 ตุลาคม 2026) LIFF browser จะไม่เปลี่ยนเครื่องหมาย
?ในค่าของ query เป็น&อีกแล้ว ควรทดสอบหน้าที่รับ URL ผ่าน query parameter
#Layout เวอร์ชัน และข้อจำกัด
- เลือกขนาดหน้าจอตามการใช้งาน:
Fullสำหรับขั้นตอนหลายหน้าCompactหรือTallสำหรับฟอร์มสั้น ๆ ที่ลอยเหนือแชท อย่ากำหนดความสูงตายตัว และเว้นระยะด้วยenv(safe-area-inset-bottom) - ตั้งแต่ LINE 26.7.0 ส่วน header ของ LIFF browser แสดงปุ่มจุดสามจุดแนวตั้งที่เปิดเมนู (แท็บทั้งหมด รีเฟรช ย่อหน้าต่าง แชร์ และอื่น ๆ) แทนปุ่ม multi-tab แบบเดิม
- CDN แบบ edge จะได้เวอร์ชัน minor และ patch ใหม่อัตโนมัติ ส่วน npm และการระบุเวอร์ชันตายตัวต้องอัปเดตเอง LINE เลิกเผยแพร่ roadmap ของ LIFF ตั้งแต่ธันวาคม 2025 จึงควรติดตามข่าว LINE Developers
- ห้าม load test ผ่าน
https://liff.line.me/หรือยิง LIFF API จำนวนมาก development guidelines ของ LINE ห้ามไว้ - อย่าติดตามผู้ใช้ด้วย cookie หรือ localStorage หรือผูกข้อมูล LINE เข้ากับ session อื่นโดยไม่ได้รับความยินยอม และเมื่อผู้ใช้ลบบัญชีในบริการของคุณ ให้ยกเลิกการอนุญาตแอป (deauthorize) ผ่าน LINE Login API
#Deploy และทดสอบ
- Deploy ขึ้นโฮสต์ที่เป็น HTTPS (Vercel, Cloud Run หรือเซิร์ฟเวอร์ของคุณเอง) และตั้งค่า
NEXT_PUBLIC_LIFF_IDกับLINE_LOGIN_CHANNEL_ID - ตั้ง Endpoint URL ของ LIFF app เป็น URL ที่ deploy แล้ว หรือ sub-path ที่แอปอยู่
- เปิด LIFF URL จากแชทบน iOS และ Android
#พัฒนาบนเครื่องด้วย LIFF CLI
LIFF CLI ของ LINE จะเปิด HTTPS proxy ไว้หน้าเซิร์ฟเวอร์บนเครื่อง และตั้ง endpoint URL ของ LIFF app ให้ชี้มาที่ proxy นั้น:
npm install -g @line/liff-cli
liff-cli channel add 1234567890 # จะถาม channel secret
liff-cli serve --liff-id 1234567890-AbcdEfgh --url http://localhost:3000/
# ถ้าจะเปิดบนมือถือ ให้ใช้ ngrok แทน proxy บนเครื่อง
NGROK_AUTHTOKEN=your-token liff-cli serve --liff-id 1234567890-AbcdEfgh --url http://localhost:3000/ --proxy-type ngrok
คำสั่ง serve จะเขียนทับ endpoint URL จึงควรใช้กับ LIFF app สำหรับพัฒนาแยกต่างหาก ห้ามใช้กับแอปที่เผยแพร่แล้ว เพิ่ม --inspect เพื่อ debug ด้วย LIFF Inspector และลองเมธอดต่าง ๆ ของ SDK ได้โดยไม่ต้องเขียนโค้ดใน LIFF Playground
#Checklist การทดสอบ
- เปิด LIFF URL จากแชท 1:1 และแชทกลุ่ม บน iOS และ Android
- เปิด endpoint URL ใน in-app browser ของ LINE: ฟีเจอร์เฉพาะ LIFF ถูกซ่อน และไม่มีอะไรพัง
- External browser: ล็อกอินแล้วกลับมาหน้าเดิม
-
sendMessagesจากแชท และกรณี 403 เมื่อไม่ได้รับอนุญาต - Share target picker: ส่งสำเร็จ และกรณีผู้ใช้ปิดหน้าเลือก
- Deep link ที่มี path และ query รวมถึงค่า query ที่เป็น URL
- เซิร์ฟเวอร์ปฏิเสธ ID token ที่ถูกแก้ไขหรือหมดอายุ
- LIFF SDK เวอร์ชัน 2.31.1 ขึ้นไป
#LIFF App กับ LineBot.pro
LineBot.pro ไม่ได้สร้าง ไม่ได้โฮสต์ และไม่มีเทมเพลต LIFF app รวมถึงไม่มี LIFF SDK หรือ public API แต่มีเครื่องมือสองอย่างที่ใช้ร่วมกับโปรเจกต์ LIFF ได้:
- ข้อความที่จะส่งจากแอป เครื่องมือสร้าง Rich Message ด้วย AI สร้าง Flex bubble (ภาพ hero ข้อความ และปุ่ม) ที่ดาวน์โหลดหรือคัดลอกเป็น JSON ไปใส่ใน
liff.sendMessages()หรือliff.shareTargetPicker()ได้ ถ้าใช้กับ picker ให้เหลือเฉพาะปุ่มที่เปิดลิงก์ (URI action) เพราะปุ่มที่ส่งข้อความใช้ไม่ได้ Rich Message หนึ่งชิ้นใช้ 8 เครดิต - ทางเข้าแอปของคุณ เครื่องมือสร้าง Rich Menu สร้างรูปเมนูและ JSON แบบ 1 ถึง 6 ปุ่มในราคา 10 เครดิต LineBot.pro ไม่ได้เผยแพร่เมนูให้ ต้องอัปโหลดรูปเองใน LINE Official Account Manager แล้วตั้งลิงก์ของปุ่มเป็น LIFF URL ที่นั่น หรือส่ง JSON ผ่าน Messaging API
สมัครใหม่รับฟรี 50 เครดิต เครดิตซื้อเป็นแพ็กแบบจ่ายครั้งเดียว ไม่มีวันหมดอายุ และไม่มีรายเดือน (ราคา) ถ้าต้องการตัว LIFF app เอง LINE MINI App หรือ backend ที่เชื่อมกับ Messaging API นั่นคืองานพัฒนาตามความต้องการ ซึ่งเสนอราคาเป็นรายโปรเจกต์
#คำถามที่พบบ่อย
Q: LIFF ย่อมาจากอะไร?
LINE Front-end Framework คือแพลตฟอร์มของ LY Corporation สำหรับเว็บแอปที่เปิดภายใน LINE และใช้ตัวตน LINE ของผู้ใช้ได้ ตัว SDK คือแพ็กเกจ npm @line/liff หรือสคริปต์ https://static.line-scdn.net/liff/edge/2/sdk.js
Q: ทำไม endpoint URL ของ LIFF ต้องเป็น HTTPS?
LINE รับเฉพาะ endpoint URL แบบ https ที่ไม่มี fragment # ถ้าแอปหรือเนื้อหาที่แอปเปิดใช้ http LINE จะแสดงใน in-app browser และแอปจะไม่ทำงานแบบ LIFF อีก ให้ใช้โฮสต์ HTTPS หรือใช้ proxy ของ LIFF CLI หรือ ngrok ระหว่างพัฒนา
Q: liff.getProfile() คืนค่า user ID ไหม?
คืน ทั้ง userId และ displayName รวมถึง pictureUrl และ statusMessage ถ้าตั้งไว้ เมื่อผู้ใช้อนุญาต scope profile แต่ใช้เพื่อแสดงผลเท่านั้น ถ้าจะบันทึกลงฐานข้อมูล ให้ส่ง liff.getIDToken() ไปที่เซิร์ฟเวอร์แล้วใช้ sub จาก token ที่ยืนยันแล้ว และ user ID จะต่างกันในแต่ละ provider
Q: ควรสร้าง LIFF app หรือ LINE MINI App? LINE แนะนำให้สร้างแอปใหม่เป็น LINE MINI App เพราะกำลังรวม LIFF เข้ากับ LINE MINI App ทั้งสองแบบใช้ LIFF SDK ตัวเดียวกัน แต่ MINI App channel มีขั้นตอน verification review และฟีเจอร์สำหรับแอปที่ผ่านการยืนยัน และตั้งแต่ 11 มีนาคม 2026 MINI App แบบ unverified เผยแพร่ในไทยและไต้หวันได้แล้ว
Q: ทำไม liff.sendMessages() ขึ้น error 403?
เมธอดนี้ใช้ได้เฉพาะใน LIFF browser ในแอปที่เปิดจากแชท เมื่อได้รับอนุญาต chat_message.write และไม่ได้โหลดใหม่จากบริการที่ใช้ล่าสุด การเปิด endpoint URL ตรง ๆ การเปิดจาก Keep Memo หรือหลัง redirect จากเว็บไซต์ก็จะล้มเหลว
Q: ควรใช้ LIFF SDK เวอร์ชันไหน? v2.31.1 ขึ้นไป โดย v2.31.2 เป็นเวอร์ชันล่าสุด (ตุลาคม 2026) เมื่อ 30 กันยายน 2026 LINE ประกาศ deprecated v2.20.0 ถึง v2.31.0 เพราะปัญหาที่อาจกระทบความปลอดภัย CDN แบบ edge อัปเดตเอง แต่ npm และ CDN ที่ระบุเวอร์ชันตายตัวไม่อัปเดตให้
ขั้นตอนต่อไป:
- ต้องการให้ทีมพัฒนาให้? ดูบริการพัฒนาแอปบน LINE สำหรับ LIFF และ LINE MINI App
- เชื่อม backend ด้วยบทสอนเชื่อมต่อ LINE API
- ติดตามการเปลี่ยนแปลงล่าสุดในอัปเดต LINE Messaging API 2026
- เพิ่มบอทหลังแอปด้วยบทสอนเขียน LINE Bot
บริการที่เกี่ยวข้อง
เกี่ยวกับ LineBot.pro
LineBot.pro คือเครื่องมือ AI สำหรับ LINE Official Account ที่ช่วยสร้าง Rich Menu, Rich Message และแบนเนอร์ ส่งแคมเปญบรอดแคสต์ และตอบกลับอัตโนมัติด้วยคำสำคัญ ทีมงานของเราเขียนคู่มือเหล่านี้จากประสบการณ์จริงกับ LINE Messaging API
ติดต่อเราบทความที่เกี่ยวข้อง

ตัวอย่าง @line/bot-sdk Express Middleware (v11, TypeScript)
โค้ด webhook ด้วย Express + TypeScript สำหรับ @line/bot-sdk v11 ที่ตรวจ type แล้ว: วาง middleware() ตรงไหน, validateSignature ทำงานอย่างไรใน Node, Python และ PHP, replyMessage พร้อม Flex Message, sentMessages กับ quote token และ error ที่เจอจริง
อ่านเพิ่มเติม
อัปเดต LINE Messaging API 2026: อะไรเปลี่ยนบ้างสำหรับนักพัฒนา
สรุปทุกการเปลี่ยนแปลงของ LINE Messaging API, LIFF และ LINE MINI App ตั้งแต่ปี 2025 ถึงตุลาคม 2026 ที่กระทบบอทที่ใช้งานอยู่ ทั้ง endpoint ใหม่ rate limit ที่ลดลง อัปเดตความปลอดภัย LIFF v2.31.1, Node.js SDK v11, MINI App ในไทยและไต้หวัน และจำนวนข้อความฟรีของแต่ละประเทศ
อ่านเพิ่มเติม
สอนเขียน LINE Bot ด้วย Node.js และ Python (Messaging API) 2026
สอนเขียน LINE Bot ทีละขั้นด้วย Messaging API ตั้งแต่เปิดใช้ channel ตรวจลายเซ็น webhook ตอบด้วย Flex Message และ quick reply ทำขั้นตอนจองคิวด้วย postback เพิ่มคำตอบจาก AI ที่จำกัดอยู่ใน FAQ ของร้าน ไปจนถึง deploy พร้อมโค้ด Node.js (@line/bot-sdk v11) และ Python (line-bot-sdk v3)
อ่านเพิ่มเติมพร้อมให้ LINE OA ของคุณทำงานอัตโนมัติหรือยัง?
เริ่มสร้างคอนเทนต์และระบบตอบกลับอัตโนมัติบน LINE กับ LineBot.pro วันนี้