Template components
The shape of a template
components is an array. Each entry names a type, and depending on that type carries format, text, buttons or example. Order in the array does not matter; WhatsApp always renders header, body, footer, buttons.
| Component | How many | Carries |
|---|---|---|
HEADER | 0 or 1 | format, plus text or an example handle |
BODY | Exactly 1 | text, plus example values for its variables |
FOOTER | 0 or 1 | text only — no variables, no formatting |
BUTTONS | 0 or 1 | a buttons array |
Two sets of limits
What we accept is not what Meta approves
| Limit | This API rejects above | Meta approves up to |
|---|---|---|
| Header text length | 1024 | 60 |
| Body text length | 1024 | 1024 |
| Footer text length | 1024 | 60 |
| Number of buttons | 10 | 3Meta allows more only for specific button mixes. |
| Button label length | 25 | 25 |
| Button URL length | 2000 | 2000 |
| Button phone number length | 20 | 20 |
Header — by format
A header is optional, and there may be at most one. Its format decides everything else about it.
format: TEXT
Up to 60 characters at Meta. May contain a single variable, and if it does you must supply an example.
{ "type": "HEADER", "format": "TEXT", "text": "Order update" }{
"type": "HEADER",
"format": "TEXT",
"text": "Order {{1}} update",
"example": { "header_text": ["4471"] }
}A text header variable cannot be filled at send time
{{1}} will be sent without a header parameter and rejected by Meta. Keep text headers static.format: IMAGE, VIDEO or DOCUMENT
A media header carries no text. It needs a sample uploaded first, whose handle goes in example.header_handle. The full flow is on the /docs/templates/media page.
{
"type": "HEADER",
"format": "IMAGE",
"example": { "header_handle": ["4::aW1hZ2UvcG5n:ARZ…"] }
}A media header needs a real media id at send time, which /api/messages/send does not supply — such a template has to be sent from the dashboard.
format: LOCATION
Accepted, but unsupported
LOCATION passes validation and is stored, but nothing in the send path supplies a location parameter, so such a template cannot actually be sent from here. Treat it as unavailable.Body
The only required component. Up to 1024 characters, and the only place variables are actually filled at send time.
{ "type": "BODY", "text": "Your order has shipped and will arrive within two days." }{
"type": "BODY",
"text": "Hi {{1}}, order {{2}} ships on {{3}}.",
"example": { "body_text": [["Priya", "4471", "29 August"]] }
}- Variables are numbered from
{{1}}and must be sequential with no gaps. - A variable may not sit at the very start or end of the body, and two may not be adjacent — Meta rejects both.
- Every variable needs an example value. Meta rejects a template that has variables and no examples; this API does not check that for you.
Example values
Reviewers see example values, not placeholders. The three shapes are genuinely different and getting them wrong is the most common cause of a rejected submission:
| Where | Key | Shape |
|---|---|---|
| Text header | header_text | A flat array of strings |
| Media header | header_handle | An array holding one handle string |
| Body | body_text | An array holding one array of strings |
"example": { "header_text": ["4471"] }
"example": { "header_handle": ["4::aW1hZ2UvcG5n:ARZ…"] }
"example": { "body_text": [["Priya", "4471", "29 August"]] }Note the nesting on body_text: an array containing one array. A flat array there is the single most frequent mistake.
A complete template
Media header, body with three variables, footer and two buttons:
{
"wabaAccountId": "68a1f3…",
"name": "order_shipped_v2",
"language": "en_US",
"category": "UTILITY",
"headerMediaFile": {
"filename": "3f9a1c…",
"mimeType": "image/png",
"size": 184320
},
"components": [
{
"type": "HEADER",
"format": "IMAGE",
"example": { "header_handle": ["4::aW1hZ2UvcG5n:ARZ…"] }
},
{
"type": "BODY",
"text": "Hi {{1}}, order {{2}} is on its way and should arrive by {{3}}.",
"example": { "body_text": [["Priya", "4471", "29 August"]] }
},
{ "type": "FOOTER", "text": "Reply STOP to opt out" },
{
"type": "BUTTONS",
"buttons": [
{ "type": "URL", "text": "Track shipment", "url": "https://example.com/track" },
{ "type": "QUICK_REPLY", "text": "Talk to support" }
]
}
]
}headerMediaFile comes from the upload response and is what lets us keep a readable copy of the sample — Meta never gives one back.