[{"data":1,"prerenderedAt":893},["ShallowReactive",2],{"h1974442968":3,"h4089069965":541,"h2541200920":886},{"id":4,"title":5,"body":6,"description":28,"extension":532,"meta":533,"navigation":536,"path":537,"seo":538,"stem":539,"__hash__":540},"docs\u002Fen\u002Ftips\u002F2026\u002F06\u002F11\u002Ftext-v2-message-design-history.md","How the Messaging API development team designed the text message (v2) syntax",{"type":7,"value":8,"toc":518},"minimark",[9],[10,11,12,20,25,29,43,46,49,54,57,241,260,273,280,283,316,331,346,353,367,400,406,431,454,468,479,498,502,505,508,512],"Tips",{},[13,14,17],"h1",{"id":15,"class":16},"","!mb-4",[18,19],"page-title",{},[21,22],"display-date",{"date":23,"class":24},"2026\u002F06\u002F11","!mb-20",[26,27,28],"p",{},"Hi, I'm Habara, an engineer working on the Messaging API.",[26,30,31,32,37,38,42],{},"The Messaging API includes a message type called ",[33,34,36],"a",{"href":35},"\u002Freference\u002Fmessaging-api\u002F#text-message-v2","text message (v2)",", which makes it easy to embed mentions and LINE emojis. Unlike the original ",[33,39,41],{"href":40},"\u002Freference\u002Fmessaging-api\u002F#text-message","text message",", you can specify where mentions and emojis appear directly within the text body.",[26,44,45],{},"In this article, I'd like to walk through the design decisions behind the current text message (v2) syntax.",[47,48],"toc",{},[50,51,53],"h2",{"id":52},"textv2-syntax","The text message (v2) syntax",[26,55,56],{},"Before getting into the design story, let's take a quick look at the syntax of text message (v2).",[58,59,63],"pre",{"className":60,"code":61,"language":62,"meta":15,"style":15},"language-json shiki shiki-themes github-dark-default","{\n  \"type\": \"textV2\",\n  \"text\": \"Hello {you}! {smile}\",\n  \"substitution\": {\n    \"you\": {\n      \"type\": \"mention\",\n      \"mentionee\": {\n        \"type\": \"user\",\n        \"userId\": \"U0123456789ab...\"\n      }\n    },\n    \"smile\": {\n      \"type\": \"emoji\",\n      \"productId\": \"...\",\n      \"emojiId\": \"...\"\n    }\n  }\n}\n","json",[64,65,66,75,92,105,114,122,135,143,156,167,173,179,187,199,212,223,229,235],"code",{"__ignoreMap":15},[67,68,71],"span",{"class":69,"line":70},"line",1,[67,72,74],{"class":73},"sZEs4","{\n",[67,76,78,82,85,89],{"class":69,"line":77},2,[67,79,81],{"class":80},"sPWt5","  \"type\"",[67,83,84],{"class":73},": ",[67,86,88],{"class":87},"s9uIt","\"textV2\"",[67,90,91],{"class":73},",\n",[67,93,95,98,100,103],{"class":69,"line":94},3,[67,96,97],{"class":80},"  \"text\"",[67,99,84],{"class":73},[67,101,102],{"class":87},"\"Hello {you}! {smile}\"",[67,104,91],{"class":73},[67,106,108,111],{"class":69,"line":107},4,[67,109,110],{"class":80},"  \"substitution\"",[67,112,113],{"class":73},": {\n",[67,115,117,120],{"class":69,"line":116},5,[67,118,119],{"class":80},"    \"you\"",[67,121,113],{"class":73},[67,123,125,128,130,133],{"class":69,"line":124},6,[67,126,127],{"class":80},"      \"type\"",[67,129,84],{"class":73},[67,131,132],{"class":87},"\"mention\"",[67,134,91],{"class":73},[67,136,138,141],{"class":69,"line":137},7,[67,139,140],{"class":80},"      \"mentionee\"",[67,142,113],{"class":73},[67,144,146,149,151,154],{"class":69,"line":145},8,[67,147,148],{"class":80},"        \"type\"",[67,150,84],{"class":73},[67,152,153],{"class":87},"\"user\"",[67,155,91],{"class":73},[67,157,159,162,164],{"class":69,"line":158},9,[67,160,161],{"class":80},"        \"userId\"",[67,163,84],{"class":73},[67,165,166],{"class":87},"\"U0123456789ab...\"\n",[67,168,170],{"class":69,"line":169},10,[67,171,172],{"class":73},"      }\n",[67,174,176],{"class":69,"line":175},11,[67,177,178],{"class":73},"    },\n",[67,180,182,185],{"class":69,"line":181},12,[67,183,184],{"class":80},"    \"smile\"",[67,186,113],{"class":73},[67,188,190,192,194,197],{"class":69,"line":189},13,[67,191,127],{"class":80},[67,193,84],{"class":73},[67,195,196],{"class":87},"\"emoji\"",[67,198,91],{"class":73},[67,200,202,205,207,210],{"class":69,"line":201},14,[67,203,204],{"class":80},"      \"productId\"",[67,206,84],{"class":73},[67,208,209],{"class":87},"\"...\"",[67,211,91],{"class":73},[67,213,215,218,220],{"class":69,"line":214},15,[67,216,217],{"class":80},"      \"emojiId\"",[67,219,84],{"class":73},[67,221,222],{"class":87},"\"...\"\n",[67,224,226],{"class":69,"line":225},16,[67,227,228],{"class":73},"    }\n",[67,230,232],{"class":69,"line":231},17,[67,233,234],{"class":73},"  }\n",[67,236,238],{"class":69,"line":237},18,[67,239,240],{"class":73},"}\n",[26,242,243,244,247,248,251,252,255,256,259],{},"Inside ",[64,245,246],{},"text",", you write placeholders like ",[64,249,250],{},"{you}"," or ",[64,253,254],{},"{smile}",", and ",[64,257,258],{},"substitution"," defines what each placeholder maps to: a mention, a LINE emoji, and so on.",[26,261,262,263,265,266,269,270,272],{},"In this syntax, the replacement content is specified in a separate ",[64,264,258],{}," property. We also defined ",[64,267,268],{},"textV2"," as a new type instead of extending the existing ",[64,271,246],{}," type. From here on, we'll look back at each of these design decisions in turn.",[50,274,276,277,279],{"id":275},"why-substitution-property","Why we chose a ",[64,278,258],{}," property for replacement",[26,281,282],{},"The first candidate we considered was a format that packs all the information about mentions and emojis directly into the text itself. The syntax would have looked something like this:",[58,284,286],{"className":60,"code":285,"language":62,"meta":15,"style":15},"{\n  \"type\": \"text\",\n  \"text\": \"Hello {@U0123456789ab...}! {:smile:}\"\n}\n",[64,287,288,292,303,312],{"__ignoreMap":15},[67,289,290],{"class":69,"line":70},[67,291,74],{"class":73},[67,293,294,296,298,301],{"class":69,"line":77},[67,295,81],{"class":80},[67,297,84],{"class":73},[67,299,300],{"class":87},"\"text\"",[67,302,91],{"class":73},[67,304,305,307,309],{"class":69,"line":94},[67,306,97],{"class":80},[67,308,84],{"class":73},[67,310,311],{"class":87},"\"Hello {@U0123456789ab...}! {:smile:}\"\n",[67,313,314],{"class":69,"line":107},[67,315,240],{"class":73},[26,317,318,319,322,323,326,327,330],{},"We decided not to go with this format. That's because we wanted to prevent situations where syntax misuse leads to user ID exposure. For example, if the braces (",[64,320,321],{},"{"," and ",[64,324,325],{},"}",") were omitted and ",[64,328,329],{},"Hello @U0123456789ab...!"," were sent, the raw user ID that should have become a mention would appear directly in the recipient's chat. LINE Official Accounts are used by businesses and shops as channels for communicating with their customers. That's why we needed a conservative, accident-resistant design.",[26,332,333,334,336,337,339,340,342,343,345],{},"Instead, we landed on the ",[64,335,258],{},"-based approach shown at the top of the article. With this approach, sensitive information like user IDs stays inside ",[64,338,258],{},". As a result, even with a typo in ",[64,341,246],{},", the worst the recipient sees is a placeholder name like ",[64,344,250],{},". This structure makes it less likely for the raw user ID to leak into the message body.",[50,347,349,350,352],{"id":348},"why-new-type","Why we introduced a new ",[64,351,268],{}," type",[26,354,355,356,358,359,322,361,363,364,366],{},"At first, we considered extending the existing ",[64,357,246],{}," type rather than creating a new one. However, handling placeholders requires treating ",[64,360,321],{},[64,362,325],{}," as control characters, and applying this rule to the existing ",[64,365,246],{}," type would break backward compatibility.",[26,368,369,370,322,372,374,375,377,378,381,382,322,385,388,389,391,392,395,396,399],{},"Because ",[64,371,321],{},[64,373,325],{}," are treated as control characters, for the ",[64,376,268],{}," type we adopted the escape syntax used in Python's ",[64,379,380],{},"str.format"," (",[64,383,384],{},"{{",[64,386,387],{},"}}",") for cases where they need to be sent as plain text. If we applied this rule to the ",[64,390,246],{}," type, implementations that had been sending a string like ",[64,393,394],{},"{{example}}"," as-is would send ",[64,397,398],{},"{example}"," instead. We couldn't change the behavior of existing code like that.",[26,401,402,403,405],{},"In other words, the ",[64,404,246],{}," type would have needed two distinct modes:",[407,408,409,423],"ul",{},[410,411,412,413,322,415,417,418,251,420,422],"li",{},"A mode where ",[64,414,321],{},[64,416,325],{}," are control characters, requiring ",[64,419,384],{},[64,421,387],{}," to use them as literal characters",[410,424,425,426,322,428,430],{},"The existing mode, where ",[64,427,321],{},[64,429,325],{}," are treated as ordinary characters",[26,432,433,434,436,437,439,440,442,443,446,447,450,451,453],{},"One way to distinguish these two would be to switch on whether a ",[64,435,258],{}," property is present. Developers using the new mode would naturally specify ",[64,438,258],{},", while existing code in the old mode wouldn't use this property, so this seems like a natural approach. However, if we split ",[64,441,258],{},"'s state into three cases (\"has entries\", \"",[64,444,445],{},"{}"," (empty object)\", and \"",[64,448,449],{},"null"," or unspecified\"), the question becomes which side the middle case (",[64,452,445],{},") belongs to.",[26,455,456,457,459,460,322,462,464,465,467],{},"If we group ",[64,458,445],{}," with the new mode, then ",[64,461,445],{},[64,463,449],{}," would behave differently. In a language or framework that treats both empty objects and ",[64,466,449],{}," as empty values, the message could end up being sent in a different mode than the developer intended. This is one of the traps to watch out for when designing JSON-based APIs.",[26,469,470,471,473,474,322,476,478],{},"Conversely, if we group ",[64,472,445],{}," with the old mode, the zero-substitution case requires one of two workarounds: either skip the escape processing for ",[64,475,321],{},[64,477,325],{}," as a special case, or insert a dummy entry to switch to the new mode. Either way, developers would need to add extra handling that shouldn't normally be necessary.",[26,480,481,482,484,485,488,489,491,492,494,495,497],{},"Since neither classification works cleanly, we abandoned switching modes based on the ",[64,483,258],{}," property and decided to lock the mode at the ",[64,486,487],{},"type"," level. That is the main reason we introduced a separate ",[64,490,268],{}," type. The ",[64,493,246],{}," type continues to behave exactly as before, while we could design the ",[64,496,268],{}," type without backward-compatibility constraints.",[50,499,501],{"id":500},"wrap-up","Wrap-up",[26,503,504],{},"Designing even a single syntax meant balancing more than usability: the risk of misuse, the impact on existing code, and how to interpret JSON-expressible values at the API level.",[26,506,507],{},"API design stories like this usually stay behind the scenes, but I hope you enjoyed this little glimpse into how the syntax came together.",[509,510,511],"style",{},"html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",[513,514],"tags",{"tags":515,"lang":516,"section":517},"messaging-api","en","tips",{"title":15,"searchDepth":107,"depth":107,"links":519},[520,523,526,529],{"id":521,"depth":77,"text":522},"the-text-message-v2-syntax-textv2-syntax","The text message (v2) syntax {#textv2-syntax}",{"id":524,"depth":77,"text":525},"why-we-chose-a-substitution-property-for-replacement-why-substitution-property","Why we chose a substitution property for replacement {#why-substitution-property}",{"id":527,"depth":77,"text":528},"why-we-introduced-a-new-textv2-type-why-new-type","Why we introduced a new textV2 type {#why-new-type}",{"id":530,"depth":77,"text":531},"wrap-up-wrap-up","Wrap-up {#wrap-up}","md",{"date":534,"tags":515,"locale":516,"sidebar":535},"2026-06-11 00:00 UTC",false,true,"\u002Fen\u002Ftips\u002F2026\u002F06\u002F11\u002Ftext-v2-message-design-history",{"title":5,"description":28},"en\u002Ftips\u002F2026\u002F06\u002F11\u002Ftext-v2-message-design-history","W8r7fsoKbgsHhv7SvdGwwtgPb6qM7xWGVuwi1LK_E0w",{"id":4,"title":5,"body":542,"description":28,"extension":532,"meta":884,"navigation":536,"path":537,"seo":885,"stem":539,"__hash__":540},{"type":7,"value":543,"toc":878},[544],[10,545,546,550,552,554,560,562,564,566,568,696,706,714,718,720,750,758,768,772,782,802,806,824,838,848,856,868,870,872,874,876],{},[13,547,548],{"id":15,"class":16},[18,549],{},[21,551],{"date":23,"class":24},[26,553,28],{},[26,555,31,556,37,558,42],{},[33,557,36],{"href":35},[33,559,41],{"href":40},[26,561,45],{},[47,563],{},[50,565,53],{"id":52},[26,567,56],{},[58,569,570],{"className":60,"code":61,"language":62,"meta":15,"style":15},[64,571,572,576,586,596,602,608,618,624,634,642,646,650,656,666,676,684,688,692],{"__ignoreMap":15},[67,573,574],{"class":69,"line":70},[67,575,74],{"class":73},[67,577,578,580,582,584],{"class":69,"line":77},[67,579,81],{"class":80},[67,581,84],{"class":73},[67,583,88],{"class":87},[67,585,91],{"class":73},[67,587,588,590,592,594],{"class":69,"line":94},[67,589,97],{"class":80},[67,591,84],{"class":73},[67,593,102],{"class":87},[67,595,91],{"class":73},[67,597,598,600],{"class":69,"line":107},[67,599,110],{"class":80},[67,601,113],{"class":73},[67,603,604,606],{"class":69,"line":116},[67,605,119],{"class":80},[67,607,113],{"class":73},[67,609,610,612,614,616],{"class":69,"line":124},[67,611,127],{"class":80},[67,613,84],{"class":73},[67,615,132],{"class":87},[67,617,91],{"class":73},[67,619,620,622],{"class":69,"line":137},[67,621,140],{"class":80},[67,623,113],{"class":73},[67,625,626,628,630,632],{"class":69,"line":145},[67,627,148],{"class":80},[67,629,84],{"class":73},[67,631,153],{"class":87},[67,633,91],{"class":73},[67,635,636,638,640],{"class":69,"line":158},[67,637,161],{"class":80},[67,639,84],{"class":73},[67,641,166],{"class":87},[67,643,644],{"class":69,"line":169},[67,645,172],{"class":73},[67,647,648],{"class":69,"line":175},[67,649,178],{"class":73},[67,651,652,654],{"class":69,"line":181},[67,653,184],{"class":80},[67,655,113],{"class":73},[67,657,658,660,662,664],{"class":69,"line":189},[67,659,127],{"class":80},[67,661,84],{"class":73},[67,663,196],{"class":87},[67,665,91],{"class":73},[67,667,668,670,672,674],{"class":69,"line":201},[67,669,204],{"class":80},[67,671,84],{"class":73},[67,673,209],{"class":87},[67,675,91],{"class":73},[67,677,678,680,682],{"class":69,"line":214},[67,679,217],{"class":80},[67,681,84],{"class":73},[67,683,222],{"class":87},[67,685,686],{"class":69,"line":225},[67,687,228],{"class":73},[67,689,690],{"class":69,"line":231},[67,691,234],{"class":73},[67,693,694],{"class":69,"line":237},[67,695,240],{"class":73},[26,697,243,698,247,700,251,702,255,704,259],{},[64,699,246],{},[64,701,250],{},[64,703,254],{},[64,705,258],{},[26,707,262,708,265,710,269,712,272],{},[64,709,258],{},[64,711,268],{},[64,713,246],{},[50,715,276,716,279],{"id":275},[64,717,258],{},[26,719,282],{},[58,721,722],{"className":60,"code":285,"language":62,"meta":15,"style":15},[64,723,724,728,738,746],{"__ignoreMap":15},[67,725,726],{"class":69,"line":70},[67,727,74],{"class":73},[67,729,730,732,734,736],{"class":69,"line":77},[67,731,81],{"class":80},[67,733,84],{"class":73},[67,735,300],{"class":87},[67,737,91],{"class":73},[67,739,740,742,744],{"class":69,"line":94},[67,741,97],{"class":80},[67,743,84],{"class":73},[67,745,311],{"class":87},[67,747,748],{"class":69,"line":107},[67,749,240],{"class":73},[26,751,318,752,322,754,326,756,330],{},[64,753,321],{},[64,755,325],{},[64,757,329],{},[26,759,333,760,336,762,339,764,342,766,345],{},[64,761,258],{},[64,763,258],{},[64,765,246],{},[64,767,250],{},[50,769,349,770,352],{"id":348},[64,771,268],{},[26,773,355,774,358,776,322,778,363,780,366],{},[64,775,246],{},[64,777,321],{},[64,779,325],{},[64,781,246],{},[26,783,369,784,322,786,374,788,377,790,381,792,322,794,388,796,391,798,395,800,399],{},[64,785,321],{},[64,787,325],{},[64,789,268],{},[64,791,380],{},[64,793,384],{},[64,795,387],{},[64,797,246],{},[64,799,394],{},[64,801,398],{},[26,803,402,804,405],{},[64,805,246],{},[407,807,808,818],{},[410,809,412,810,322,812,417,814,251,816,422],{},[64,811,321],{},[64,813,325],{},[64,815,384],{},[64,817,387],{},[410,819,425,820,322,822,430],{},[64,821,321],{},[64,823,325],{},[26,825,433,826,436,828,439,830,442,832,446,834,450,836,453],{},[64,827,258],{},[64,829,258],{},[64,831,258],{},[64,833,445],{},[64,835,449],{},[64,837,445],{},[26,839,456,840,459,842,322,844,464,846,467],{},[64,841,445],{},[64,843,445],{},[64,845,449],{},[64,847,449],{},[26,849,470,850,473,852,322,854,478],{},[64,851,445],{},[64,853,321],{},[64,855,325],{},[26,857,481,858,484,860,488,862,491,864,494,866,497],{},[64,859,258],{},[64,861,487],{},[64,863,268],{},[64,865,246],{},[64,867,268],{},[50,869,501],{"id":500},[26,871,504],{},[26,873,507],{},[509,875,511],{},[513,877],{"tags":515,"lang":516,"section":517},{"title":15,"searchDepth":107,"depth":107,"links":879},[880,881,882,883],{"id":521,"depth":77,"text":522},{"id":524,"depth":77,"text":525},{"id":527,"depth":77,"text":528},{"id":530,"depth":77,"text":531},{"date":534,"tags":515,"locale":516,"sidebar":535},{"title":5,"description":28},[887,888,890,892],{"id":52,"depth":77,"text":53},{"id":275,"depth":77,"text":889},"Why we chose a substitution property for replacement",{"id":348,"depth":77,"text":891},"Why we introduced a new textV2 type",{"id":500,"depth":77,"text":501},1781161569222]