LINEミニアプリとLIFFは、名前は違っても中身は同じ仕組みです。どちらも「LINEの中で開くWebページ」で、開いた人のLINEのユーザーIDを、ログイン画面を出さずに受け取れます。違うのは、作る場所(チャネルの種類)と、審査を通したときに使える機能の範囲です。読み終わると、自社の予約や会員証にどれが要るかを決められ、サンプル料理教室の体験予約フォームをLIFFで作って、予約と友だちを結びつけるところまで手を動かせます。前提は、LINE公式アカウントを開設済みで、Messaging APIを有効にしてあることです。仕様は2026年10月時点のLINE Developersの公式ドキュメントに合わせています。
この記事の内容
先に決めること
作り始める前に、次の4つを決めます。ここを後回しにすると、チャネルを作り直すことになります。チャネルは作ったあとで別のプロバイダーへ移せません。
- ユーザーIDが要るかどうか。予約の内容を「誰の予約か」としてLINEの友だちと結びつけ、前日にその人のトークへリマインドを送りたいなら要ります。予約を受けるだけで、連絡はメールや電話で足りるなら要りません
- どのプロバイダーに作るか。ユーザーIDはプロバイダーごとに別の値が振られます。公式アカウントのMessaging APIチャネルと同じプロバイダーに作らないと、Webhookで届くユーザーIDとLIFFで取ったユーザーIDが一致せず、突き合わせができません
- LINEログインチャネルに作るか、LINEミニアプリチャネルに作るか。LINE Developersは、LIFFとLINEミニアプリのブランド統合を予定していて、新しく作るならLINEミニアプリとして作ることを勧めています。しかもLINEログインチャネルで作ったLIFFアプリは、あとからLINEミニアプリチャネルへ移せません。迷うならLINEミニアプリチャネルです
- 予約の「正」をどこに置くか。Googleスプレッドシートか、顧客管理のkintoneか、外部の予約システムか。LIFFは入口の画面でしかなく、予約枠と予約の記録は別の場所に持ちます
公式ドキュメントは、チャネルとプロバイダーを別々の会社が管理する形や、運用ツールの会社のプロバイダーの下にチャネルを作る形を、将来問題が起きうるケースとして挙げています。制作会社に頼む場合も、プロバイダーは自社のアカウントで持ち、制作会社には権限を付けて作業してもらいます。
LIFFとLINEミニアプリの違い
2026年10月時点の公式ドキュメントで確かめた違いです。LINEミニアプリは「LIFF上で実行されるウェブアプリ」と定義されていて、使うSDKもコードも同じです。
| 項目 | LIFFアプリ(LINEログインチャネル) | 未認証ミニアプリ | 認証済ミニアプリ |
|---|---|---|---|
| 作る場所 | LINEログインチャネルの「LIFF」タブ | LINEミニアプリチャネル | 同じLINEミニアプリチャネル |
| 審査 | なし | なし(作った時点で使える) | LINEヤフーの認証審査に通る必要あり。1〜2週間程度 |
| 作れる人 | LINE Developersのアカウントがあれば作れる | 日本の法人・個人事業主・個人 | 日本の法人・個人事業主(個人は対象外) |
| 1チャネルに置ける数 | 最大30件 | 1チャネルに1つ | 1チャネルに1つ |
| 画面の大きさ | Compact・Tall・Fullから選ぶ | Fullのみ | Fullのみ |
| 画面上部の表示 | エンドポイントURLのドメイン名 | タイトルとドメイン名 | 認証バッジ、タイトル、LINEミニアプリ名 |
| 開き方 | LIFF URL(https://liff.line.me/ + LIFF ID) | LIFF URL | LIFF URLに加え、LINE内の検索、ホームタブの「最近の履歴」、ホーム画面へのショートカット |
| 本人への通知 | Messaging APIのプッシュ(メッセージ通数に数える) | 同左(サービスメッセージは開発用でのテストのみ) | サービスメッセージを無料で送れる |
| 初回の同意画面 | 表示される | 「チャネル同意の簡略化」は開発用・審査用でのみ動く | 同意画面を省いてすぐ使える(日本の新規チャネルは常に有効)。省けるのはユーザーIDの同意だけで、表示名などは使う時点で許可画面が出る |
| 将来 | LINEミニアプリに統合される予定。ミニアプリへは移せない | 審査を申請すれば認証済になれる | ― |
費用として出てくるのは、ページを置くサーバー、作る手間(外注なら開発費)、保守、Messaging APIで送るメッセージの通数です。LINEヤフー for Businessの案内でも、ミニアプリの費用は要件と使う機能で変わるとされています。ミニアプリの中で有料のサービスを売るならアプリ内課金が必要で、2026年7月から手数料の適用が始まっています。
LIFFからミニアプリへの「移行」は、既存のLIFFアプリを引っ越す道ではなく、新しく作るものをミニアプリチャネルで作る方針です。Messaging APIチャネルにはLIFFアプリを追加できず、以前に追加済みのものは今後の新機能を使えません。
LIFFアプリにだけできるのは、アクションボタンを消す「モジュールモード」、1つのチャネルに複数のWebアプリを置くこと、画面をCompactやTallで開くことの3つです。これが要るならLINEログインチャネル、要らなければミニアプリチャネルで困りません。
使う側から見たLINEミニアプリ
利用者の目には、LINEの中に小さなアプリが開いたように見えます。アプリのインストールも会員登録も要りません。LINEヤフー for Businessが挙げている使い道は次のとおりです。
| 使い道 | 利用者がすること | 店側に残るもの |
|---|---|---|
| デジタル会員証 | 会員証を開いてレジで見せる | 会員番号とLINEのユーザーID、ポイント・ランク |
| モバイルオーダー | 席や来店前に注文して支払う | 注文履歴、待ち時間の短縮 |
| 来店予約・順番待ち | 日時を選んで予約、順番が来たらLINEで呼ばれる | 予約の記録、呼び出しの通知 |
| クーポン・スタンプカード | クーポンを受け取る、スタンプを貯める | 来店の記録 |
認証済ミニアプリになると、予約の確認や順番の呼び出しを「サービスメッセージ」として送れます。届く先は公式アカウントのトークではなく、「LINEミニアプリ お知らせ」というトークルームです。送れるのは利用者の操作に対する確認や応答だけで、1回の操作につき最大5回まで、広告や販促は禁止です。公式のFAQでは、サービス提供に必要な通知を無料で送れるとされています。
2025年10月1日からは外部のブラウザでも使えます。LINEを使っていない人がURLを開くと案内ページが出て、「ウェブブラウザで開く」を押すとそのまま使えます。
自社に要るかの判断
サンプル料理教室の規模なら、多くの場合LIFFで足ります。分かれ目は「ユーザーIDを取りたいか」と「LINEの中で見つけてもらう入口が要るか」の2つです。
| 状況 | 向く形 | 理由 |
|---|---|---|
| 外部の予約システムをすでに使っていて、予約の確認メールで足りている | 普通のWebページ(予約システムのURL) | ユーザーIDが要らない。リッチメニューに予約ページのURLを置けば済む |
| 予約した人に、前日と当日のリマインドをLINEで送りたい | LIFF(未認証ミニアプリかLINEログインチャネル) | 予約とユーザーIDを結びつけないと、誰に送るか決まらない |
| 問い合わせや申込みのフォームで、名前の入力を省きたい・送信した人を確実に特定したい | LIFF | IDトークンを検証すれば、なりすましのない本人のユーザーIDが取れる |
| 会員証・ポイント・注文を、LINEの中のサービスとして毎日使ってもらう | 認証済ミニアプリ | LINE内検索、ホーム画面へのショートカット、無料のサービスメッセージが使える |
| 個人で、事業として登録せずに運営している | LIFF(LINEログインチャネル)か未認証ミニアプリ | 認証済ミニアプリは個人では申請できない |
| 予約もフォームも拡張ツールで回している | ツールのフォーム機能 | 多くの拡張ツールは中でLIFFを使っている。自作は要らない |
ユーザーIDが要らないならWebページで終わりです。要るならLIFFを作り、画面サイズにこだわりがなければLINEミニアプリチャネルに置いて、未認証ミニアプリとして使い始めます。会員証や注文のように毎日開かれるサービスになってから、認証審査を申請します。
実際に運用していると、「ミニアプリを作りたい」という相談の中身はほとんどが「予約した人にLINEで連絡したい」で、LIFFとプッシュで足ります。認証済ミニアプリが効くのは、友だちでない人にもLINEの中で見つけてもらいたい場合と、通知の通数が無料分を大きく超える場合です。
サンプル料理教室で作るもの
サンプル料理教室では、リッチメニューの「体験レッスンの予約」を押すと、LIFFの予約フォームが開くようにします。動きは次のとおりです。
- 友だちがリッチメニューの「体験レッスンの予約」を押す
- LIFFの予約フォームが開き、空いている体験レッスンの日時が並ぶ
- 日時を選び、名前と電話番号を入れて送信する
- フォームはIDトークンと一緒に予約内容をGoogle Apps Scriptへ送る
- Apps ScriptはIDトークンを検証してユーザーIDを取り出し、「予約」シートに1行足す
- Apps ScriptはMessaging APIのプッシュで、本人のトークに予約確認を送る
- 前日と当日のリマインドは、「予約」シートのユーザーIDあてに送る
使う部品をまとめます。
| 部品 | 何に使うか | 値の例 |
|---|---|---|
| プロバイダー | 公式アカウントと同じもの | サンプル料理教室 |
| Messaging APIチャネル | Webhook、プッシュ | 公式アカウントで有効化したもの |
| LINEミニアプリチャネル | LIFFの予約フォームを置く | チャネル名 Sample Cooking Class Trial、日本語名「サンプル料理教室 体験予約」 |
| エンドポイントURL | フォームのHTMLを置く場所(HTTPS) | https://example.com/liff/trial/ |
| Googleスプレッドシート | 予約枠と予約の記録 | シート「枠」「予約」 |
| Google Apps Script | 枠の一覧、予約の受付、確認の送信 | Webアプリとしてデプロイ |
「枠」シートは、1行目を見出しにして 枠ID 日時 コース 定員 予約数 の5列を置きます。2行目から、T1030-19 2026/10/30 19:00 体験・和食の基本 4 0 のように並べます。
LIFFアプリを1つ作る手順
1. チャネルを作る(LINE Developersコンソール)
PCのブラウザで作業します。スマホのLINEアプリではできません。
- LINE Developersコンソールにログインする
- 公式アカウントのMessaging APIチャネルがあるプロバイダーを開く
- 新しいチャネルを作り、チャネルの種類にLINEミニアプリを選ぶ
- サービスを提供する地域は、日本の利用者向けなので日本にする
- チャネル名とチャネル説明を英語で入れ、アイコン画像を設定する(例:チャネル名
Sample Cooking Class Trial) - 規約に同意して作成する
- 作成後、「チャネル基本設定」タブの「多言語対応」で、日本語のチャネル名「サンプル料理教室 体験予約」と日本語の説明を登録する
- 同じタブで、プライバシーポリシーURLを入れる
チャネル名とチャネル説明は英語で入れるよう案内されていて、日本語は「多言語対応」で別に登録します。利用者の言語の登録が無いと英語のほうが表示されます。チャネル名は「ウェブアプリ設定」の「LIFFアプリ名」にもコピーされます。チャネル名とLIFFアプリ名には「LINE」やそれに似た文字列を使えません。チャネル説明には、実際のサービス内容をそのまま書きます。審査を申請するとき、プロバイダー名とサービス提供者が一致しているか、プライバシーポリシーでユーザー情報を取得する会社がプロバイダー名と同じかを見られます。
LINEログインチャネルで作る場合は、手順3で「LINEログイン」を選び、アプリタイプで「ウェブアプリ」を選びます。そのあと「LIFF」タブで「追加」を押すと、「LIFFアプリを追加」の画面が開き、LIFFアプリ名・サイズ(Compact・Tall・Full)・エンドポイントURL・Scope(openid・email・profile・chat_message.write)・友だち追加オプション(On (Normal)・On (Aggressive)・Off)を入れます。LIFFタブの上には、今後LINEミニアプリに統合する予定なので新しく作るならミニアプリを勧める、という案内が出ています。
2. エンドポイントURLとScopeを設定する
- 作ったチャネルの「ウェブアプリ設定」タブを開く
- エンドポイントURLに
https://example.com/liff/trial/を入れる - LIFFのScopeの設定で
openidとprofileにチェックを入れる - 「友だち追加オプション」を
On (normal)にする - 「チャネル基本設定」タブの「デフォルトのLINE公式アカウント」で「編集」を押し、サンプル料理教室の公式アカウントを選んで「更新」を押す
- 表示されたLIFF IDとLIFF URLを控える
エンドポイントURLはHTTPSでなければならず、# の付いたURLは指定できません。openid はIDトークンを取るために、profile は表示名を取るために要ります。トークへ本人名義でメッセージを送る liff.sendMessages() を使うなら chat_message.write も足します。
ミニアプリチャネルは、コンソール上は1つでも、中は開発用・審査用・本番用の3つに分かれています。開発用は常に「開発中」、本番用は常に「公開中」です。Basic認証をかけて関係者だけが開けるようにもできますが、効くのはステータスが「審査前」か「審査中」のミニアプリを、LIFFブラウザで開いたときだけです。「反映済み」のミニアプリや、LINEログインチャネルのLIFFアプリでは使えません。
友だち追加オプションの設定には条件が3つあります。公式アカウントがMessaging APIを使っていること、Messaging APIチャネルとミニアプリチャネルが同じプロバイダーにあること、操作する人がミニアプリチャネルのAdmin権限と公式アカウントの管理者権限の両方を持っていることです。設定すると、最初の同意画面に公式アカウントの友だち追加の選択肢が出ます。友だちでない人にはプッシュが届かないので、予約フォームでは外せない設定です。
3. フォームのページを書く
エンドポイントURLの場所に置く index.html です。開いたら liff.init() を待ち、空き枠を読み込んで並べ、送信時にIDトークンを付けてApps Scriptへ送ります。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>体験レッスンの予約</title>
<script charset="utf-8" src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>
</head>
<body>
<p id="hello">読み込んでいます</p>
<form id="f" hidden>
<p>参加する日時</p>
<select name="slotId" id="slot" required></select>
<p>お名前</p>
<input name="name" id="name" required>
<p>電話番号(当日の連絡用)</p>
<input name="tel" type="tel" required>
<button type="submit" id="btn">予約する</button>
</form>
<script>
const LIFF_ID = 'YOUR_LIFF_ID';
const GAS_URL = 'https://script.google.com/macros/s/YOUR_DEPLOY_ID/exec';
async function main() {
await liff.init({ liffId: LIFF_ID, withLoginOnExternalBrowser: true });
const profile = await liff.getProfile();
document.getElementById('hello').textContent = profile.displayName + 'さん、日時を選んでください';
document.getElementById('name').value = profile.displayName;
const res = await fetch(GAS_URL);
const slots = await res.json();
const sel = document.getElementById('slot');
if (slots.length === 0) {
document.getElementById('hello').textContent = 'いま予約できる体験レッスンがありません';
return;
}
slots.forEach(function (s) {
const o = document.createElement('option');
o.value = s.id;
o.textContent = s.label + '(残り' + s.left + '席)';
sel.appendChild(o);
});
document.getElementById('f').hidden = false;
}
document.getElementById('f').addEventListener('submit', async function (e) {
e.preventDefault();
document.getElementById('btn').disabled = true;
const fd = new FormData(e.target);
const res = await fetch(GAS_URL, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
body: JSON.stringify({
idToken: liff.getIDToken(),
slotId: fd.get('slotId'),
name: fd.get('name'),
tel: fd.get('tel')
})
});
const r = await res.json();
if (r.ok) {
alert('予約しました。確認のメッセージをLINEでお送りします');
if (liff.isInClient()) liff.closeWindow();
} else {
alert(r.message);
document.getElementById('btn').disabled = false;
}
});
main().catch(function (err) {
document.getElementById('hello').textContent = '開けませんでした。LINEのトーク画面から開き直してください';
console.error(err);
});
</script>
</body>
</html>
liff.getProfile() で取れるのは userId displayName pictureUrl statusMessage の4つで、profile のScopeが要ります。ここでは表示名を名前の欄に入れるだけに使い、ユーザーIDはサーバーに送りません。認証済ミニアプリで「チャネル同意の簡略化」が効いていても、省けるのはユーザーIDの同意だけなので、liff.getProfile() を呼んだ時点で表示名の利用の許可画面が出ることがあります。公式リファレンスが、liff.getProfile() で取った情報をサーバーに送らないよう書いているためです。サーバーには liff.getIDToken() の生のIDトークンを送り、サーバー側で検証してからユーザーIDを取り出します。
Content-Type を text/plain にしているのは、Apps ScriptのWebアプリが、ブラウザが事前に送る確認のリクエスト(OPTIONS)に応えられないためです。JSONの中身はそのまま文字列として送り、Apps Script側で読み直します。
SDKは「CDNエッジパス」(/edge/2/sdk.js)で読みます。v2の最新に自動で上がるので、後で書く旧バージョンの利用制限にも引っかかりません。バージョンを固定する「CDN固定パス」や npm の @line/liff を使う場合は、更新を自分で追いかけます。
4. 受け取る側のApps Scriptを書く
予約用のスプレッドシートに「枠」と「予約」のシートを作り、そのスプレッドシートの「拡張機能」から「Apps Script」を開いて貼ります。「予約」シートの1行目は 受付日時 ユーザーID 枠ID 日時 名前 電話番号 状態 にします。
const CHANNEL_ID = 'YOUR_MINIAPP_CHANNEL_ID';
const TOKEN = PropertiesService.getScriptProperties().getProperty('CHANNEL_ACCESS_TOKEN');
function doGet() {
const rows = SpreadsheetApp.getActive().getSheetByName('枠').getDataRange().getValues().slice(1);
const now = new Date();
const list = rows
.filter(function (r) { return new Date(r[1]) > now && r[3] - r[4] > 0; })
.map(function (r) {
return { id: r[0], label: Utilities.formatDate(new Date(r[1]), 'Asia/Tokyo', 'M月d日(E) HH:mm') + ' ' + r[2], left: r[3] - r[4] };
});
return json_(list);
}
function doPost(e) {
const d = JSON.parse(e.postData.contents);
const v = UrlFetchApp.fetch('https://api.line.me/oauth2/v2.1/verify', {
method: 'post',
payload: { id_token: d.idToken, client_id: CHANNEL_ID },
muteHttpExceptions: true
});
if (v.getResponseCode() !== 200) return json_({ ok: false, message: '開き直してからもう一度お試しください' });
const userId = JSON.parse(v.getContentText()).sub;
const lock = LockService.getScriptLock();
lock.waitLock(10000);
try {
const ss = SpreadsheetApp.getActive();
const slots = ss.getSheetByName('枠');
const rows = slots.getDataRange().getValues();
const i = rows.findIndex(function (r) { return r[0] === d.slotId; });
if (i < 1 || rows[i][3] - rows[i][4] <= 0) return json_({ ok: false, message: 'その日時は満席になりました。別の日時を選んでください' });
slots.getRange(i + 1, 5).setValue(rows[i][4] + 1);
const when = Utilities.formatDate(new Date(rows[i][1]), 'Asia/Tokyo', 'M月d日(E) HH:mm');
ss.getSheetByName('予約').appendRow([new Date(), userId, d.slotId, when, d.name, d.tel, '予約']);
push_(userId, d.name + 'さん、体験レッスンのご予約を受け付けました。\n日時:' + when + '\n' + rows[i][2] + '\n前日にもう一度お知らせします。');
} finally {
lock.releaseLock();
}
return json_({ ok: true });
}
function push_(to, text) {
UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + TOKEN },
payload: JSON.stringify({ to: to, messages: [{ type: 'text', text: text }] }),
muteHttpExceptions: true
});
}
function json_(obj) {
return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType(ContentService.MimeType.JSON);
}
CHANNEL_ID には、LIFFアプリを置いたチャネルのチャネルIDを入れます。Messaging APIチャネルのIDではありません。公式のIDトークン検証のページは「LINEログインチャネルのチャネルID」と書いています。ミニアプリチャネルに置いた場合は、そのチャネルIDで検証が通るかを、本番前にテスト送信で確かめます。検証の応答の sub がユーザーIDで、U で始まる33文字の文字列です。チャネルアクセストークンはコードに書かず、Apps Scriptの「プロジェクトの設定」の「スクリプト プロパティ」に CHANNEL_ACCESS_TOKEN として入れます。トークンの発行は「Messaging APIの始め方:チャネル・アクセストークン・Webhook」で書いています。
LockService は、同じ枠に2人が同時に送信しても定員を超えないためのものです。定員4人のような小さい枠ほど、5人目が入る事故が起きやすくなります。
最後に、Apps Scriptの「デプロイ」から「新しいデプロイ」を選び、種類を「ウェブアプリ」、実行するユーザーを「自分」、アクセスできるユーザーを「全員」にしてデプロイします。発行されたURLを、フォームの GAS_URL に入れます。

5. リッチメニューにLIFF URLを置く(管理画面)
PCの管理画面(LINE Official Account Manager)での手順です。
- LINE Official Account Managerにログインし、サンプル料理教室のアカウントを選ぶ
- 「トークルーム管理」から「リッチメニュー」を開く
- 使っているリッチメニューを開き、「体験レッスンの予約」のマスを選ぶ
- アクションのタイプで「URL」を選ぶ
- 入力欄に、控えたLIFF URL(
https://liff.line.me/で始まるもの)を入れる - アクションラベルに「体験レッスンの予約フォームを開く」と入れる
- 「保存」を押す
スマホのLINE公式アカウントアプリでも、メニューの「リッチメニュー」から同じ設定ができます。アクションの設定でURLを選び、同じLIFF URLを入れます。
エンドポイントURLを入れると、LIFFブラウザではなく「LINE内ブラウザ」で開き、LIFFの機能の多くが使えません。必ずLIFF URLを入れます。

6. スマホで動きを確かめる
- スマホのLINEで、サンプル料理教室のトークを開く
- リッチメニューの「体験レッスンの予約」を押す
- 初回は同意画面が出るので、公式アカウントの友だち追加にチェックが入っているのを見てから許可する
- 日時が並ぶのを見て、1つ選んで名前と電話番号を入れ、「予約する」を押す
- フォームが閉じ、トークに予約確認が届くのを見る
- スプレッドシートの「予約」シートに、ユーザーID付きで1行増えているのを見る
- 「枠」シートの予約数が1つ増えているのを見る


予約とユーザーIDが結びつくと何ができるか
「予約」シートにユーザーIDが残ると、次のことがその列だけでできるようになります。
- 前日と当日のリマインドを、予約した本人のトークに送る。送り方は「予約のリマインドをLINEで自動送信する:前日・当日の通知と無断キャンセル対策」で書いています
- 体験の翌日から、入会案内を本人に送る。入会した人とそうでない人で送り分ける
- 問い合わせのWebhookで届いたユーザーIDと突き合わせ、「体験予約済みの人からの問い合わせ」とスタッフが分かるようにする
- 顧客管理のkintoneに、予約とLINEのユーザーIDを同じレコードに入れる
プッシュはメッセージ通数に数えます。2026年10月時点で、コミュニケーションプラン(月額0円)は月200通、ライトプラン(月額5,000円)は月5,000通、スタンダードプラン(月額15,000円)は月30,000通が無料分で、追加の通数を買えるのはスタンダードプランだけです。体験予約1件につき、確認・前日・当日の3通を送るなら、月30件の予約で90通になります。一方、トーク画面で友だちから届いたメッセージに返すReply APIの応答は、通数に数えません。
応用
- 会員になった人には、同じ仕組みでユーザーIDから会員番号を引いて表示する会員証のページを作る
- 予約フォームを開いた時点で、
liff.getFriendship()で友だちかどうかを確かめる。friendFlagがfalseなら、liff.requestFriendship()で友だち追加を促す画面を出す。どちらもprofileのScopeが要り、liff.requestFriendship()は画面サイズがFullのときだけ使える - 予約の正を外部の予約システムに置いている場合は、予約システムのAPIにユーザーIDを渡せるかを先に確かめる。渡せないなら、LIFFは「予約システムのURLを開く前に、ユーザーIDと予約システムの会員番号を結びつける画面」として使う。詳しくは「LINEログインとID連携:会員情報と友だちを結びつける」で書いています
コードを書かずに同じことをしたい場合は、オープンソースの拡張ツールL Harness(GitHub Shudesu/line-harness-oss、MITライセンス)が選択肢になります。READMEには、LINEの中で完結するLIFFのフォームと、Googleカレンダーと連携したLIFFの予約が機能として載っていて、セットアップのコマンドがLIFFアプリの作成まで行います。ソフトの利用料は0円ですが、サーバー(Cloudflare)の準備と保守は自分持ちです。教室の例で組む手順は「L Harnessで予約・フォーム・ステップ配信を組む:教室の例で一通り」で書いています。Lステップなどの月額の拡張ツールにもフォーム機能があり、配信や顧客管理まで一式で任せたい会社にはそちらが合います。
開発を頼む場合の費用の考え方
LINEヤフー for Businessは、ミニアプリの導入の形を3つ挙げています。開発会社のパッケージを使う、認定の委託開発企業に作ってもらう、自社で作る、です。費用は相場の数字ではなく、次の項目の足し算で考えます。見積もりを取るときは、この表の項目ごとに分けて出してもらうと比べられます。
| 項目 | 費用が増える条件 | 減らす手 |
|---|---|---|
| 画面の数 | 予約・変更・取消・会員証・履歴と画面が増える | 最初は予約の1画面だけにし、変更と取消はチャットで受ける |
| 予約枠と記録の置き場所 | 専用のデータベースや管理画面を新しく作る | スプレッドシートかkintoneを正にする |
| 既存システムとの連携 | 予約システムやPOSのAPIとつなぐ | 連携は夜間の一括取込みにする |
| 通知 | リマインドの条件や文面の種類が多い | 確認・前日・当日の3種類に絞る |
| 認証審査の対応 | 認証済ミニアプリを目指す(審査資料、テストシナリオ、再申請) | 未認証ミニアプリで始め、使われてから申請する |
| 保守 | SDKの更新、LINE側の仕様変更への追随、障害時の対応 | 保守の範囲と月の対応時間を契約に書く |
見落としやすいのは保守です。2026年10月にもSDKの脆弱性に伴う旧バージョンの利用制限が告知されていて、作って終わりの契約だと変更のたびに追加の見積もりになります。もう1つはチャネルの持ち主で、制作会社のアカウントで作ったチャネルは別のプロバイダーへ移せず、あとで自社に引き取れません。
できないことと、その代わり
| できないこと | 代わりの手 |
|---|---|
| Messaging APIチャネルにLIFFアプリを追加する | 同じプロバイダーにLINEミニアプリチャネルかLINEログインチャネルを作って追加する |
| LINEログインチャネルのLIFFアプリを、あとからミニアプリチャネルへ移す | 移せないので、ミニアプリチャネルに作り直す。LIFF URLが変わるので、リッチメニューと過去の配信のリンクも差し替える |
| 別のプロバイダーのチャネル同士でユーザーIDを突き合わせる | 最初から公式アカウントと同じプロバイダーに作る |
| 未認証ミニアプリでサービスメッセージを本番で送る | Messaging APIのプッシュで送る。通数が多くなったら認証審査を申請する |
| 個人で認証済ミニアプリにする | 個人事業主として申請するか、未認証ミニアプリかLIFFで運用する |
| 友だち追加していない人にプッシュで予約確認を送る | 友だち追加オプションを有効にする。届かない人のためにフォームで電話番号を受けておく |
| ミニアプリで画面を半分の高さで開く | LINEログインチャネルのLIFFアプリにしてCompactかTallを選ぶ |
よく詰まるところ
| 症状 | 原因 | 直し方 |
|---|---|---|
| Messaging APIチャネルに「LIFF」タブが見当たらない | Messaging APIチャネルにはLIFFアプリを追加できない | 同じプロバイダーにLINEミニアプリチャネルを作る |
| LIFFで取ったユーザーIDが、Webhookで届くユーザーIDと一致しない | チャネルを別のプロバイダーに作った | 公式アカウントのMessaging APIチャネルと同じプロバイダーに作り直す |
| リッチメニューから開くとLIFFの機能が動かない | エンドポイントURLをそのまま貼り、LINE内ブラウザで開いている | https://liff.line.me/ で始まるLIFF URLに差し替える |
| ページを開くと真っ白、または初期化でエラーになる | liff.init() の完了前にリダイレクトや history の操作をしている | await liff.init() のあとでURLを変える処理を書く |
| 下の階層のページだけ動かない | エンドポイントURLの配下にないページで liff.init() を呼んでいる | ページをエンドポイントURLの配下に置くか、エンドポイントURLを上の階層にする |
liff.getIDToken() が null を返す | Scopeで openid を選んでいない、または利用者が許可していない | LIFFのScopeの設定に openid を入れる |
| サーバーでIDトークンの検証が失敗する | client_id にMessaging APIチャネルのIDを入れている、またはフォームを長く開いたままにしてトークンの期限が切れた | LIFFを置いたチャネルのIDにする。失敗したら開き直しを案内する |
liff.sendMessages() で403になる | chat_message.write が無効、またはトーク以外(「最近使用したサービス」の再読み込みなど)から開いている | Scopeを足す。確認はプッシュで送る形にする |
liff.requestFriendship() が使えない | 画面サイズがFullではない | LINEログインチャネルのLIFFならサイズをFullにする |
| 予約確認がトークに届かない人がいる | 友だち追加していない、またはブロックしている | 友だち追加オプションを On (normal) にし、liff.getFriendship() で確かめる |
| 2026年11月下旬から一部の利用者で開けなくなる | SDKのv2.20.0〜v2.31.0を固定パスやnpmで使っている。2026年11月24日ごろ利用制限の予定 | v2.31.1以降に上げるか、CDNエッジパスで読む |
| Apps Scriptへの送信がブラウザに止められる | Content-Type: application/json で送り、事前確認のリクエストが通らない | text/plain で送り、Apps Script側で JSON.parse する |
| 同じ枠に定員より多く予約が入る | 同時の送信を順番に処理していない | LockService で処理を1件ずつにし、書き込む直前に残り席を確かめる |
| 審査が通ったのにLINE内の検索に出てこない | 初回承認後は「検索可能にする」を押すまで検索されない | 押す。押さなくても「反映済み」から31日目の午前9時に自動で検索可能になる |
| ユーザーIDで管理していた人が別人扱いになった | LINEアカウントを削除して作り直すとユーザーIDが変わる。機種変更では変わらない | 電話番号など別の手がかりも予約に残しておき、名寄せはスタッフが判断する |
次にやること
- LINEログインとID連携:会員情報と友だちを結びつける
- LINEで予約を受ける:予約機能・外部の予約システム・自作の比べ方
- Messaging APIの始め方:チャネル・アクセストークン・Webhook
- L Harnessで予約・フォーム・ステップ配信を組む:教室の例で一通り