Template components

Every component a template may carry, what each one accepts, and the exact payload to send for it. If a template is being rejected, the answer is almost always on this page.

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.

ComponentHow manyCarries
HEADER0 or 1format, plus text or an example handle
BODYExactly 1text, plus example values for its variables
FOOTER0 or 1text only — no variables, no formatting
BUTTONS0 or 1a buttons array
How many of each Meta permits. The API counts none of this — send two BODY components and it is accepted here and rejected there.

Two sets of limits

What we accept is not what Meta approves

This API validates loosely — it caps every component's text at 1024 characters and allows ten buttons. Meta is much stricter. A payload between the two columns is accepted, submitted, and then rejected hours later by a reviewer. Build to the right-hand column.
LimitThis API rejects aboveMeta approves up to
Header text length102460
Body text length10241024
Footer text length102460
Number of buttons103Meta allows more only for specific button mixes.
Button label length2525
Button URL length20002000
Button phone number length2020

Body

The only required component. Up to 1024 characters, and the only place variables are actually filled at send time.

Static
{ "type": "BODY", "text": "Your order has shipped and will arrive within two days." }
With variables
{
  "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.

Buttons — by type

One BUTTONS component holding an array. Three buttons is the practical maximum; this API allows ten and Meta will refuse them.

QUICK_REPLY

A tappable reply. The recipient's tap arrives as an inbound message whose text is the button label, so you match on that.

Quick replies
{
  "type": "BUTTONS",
  "buttons": [
    { "type": "QUICK_REPLY", "text": "Track order" },
    { "type": "QUICK_REPLY", "text": "Talk to support" }
  ]
}

URL

Opens a link. The URL is fixed at approval time.

URL button
{
  "type": "BUTTONS",
  "buttons": [
    { "type": "URL", "text": "Track shipment", "url": "https://example.com/track" }
  ]
}

No dynamic URL suffix

Meta supports a URL button with a {{1}} suffix filled per recipient. This platform never sends button parameters, so such a button would go out with the placeholder unfilled. Use a static link, or put the tracking URL in the body where variables do work.

PHONE_NUMBER

Starts a call. Include the country code.

Call button
{
  "type": "BUTTONS",
  "buttons": [
    { "type": "PHONE_NUMBER", "text": "Call us", "phone_number": "+919876543210" }
  ]
}

COPY_CODE

Copies a code to the clipboard — for coupons and one-time passwords. Accepted by the API and rendered correctly, though the dashboard composer offers no button for it, so you can only add one through the API.

Copy code button
{
  "type": "BUTTONS",
  "buttons": [{ "type": "COPY_CODE", "text": "Copy code", "example": "DIWALI20" }]
}

Mixing types

Meta restricts which combinations are allowed — broadly, quick replies do not mix freely with call-to-action buttons, and an authentication template's buttons are fixed by its category. If a mixed set is rejected, split it into two templates.

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:

WhereKeyShape
Text headerheader_textA flat array of strings
Media headerheader_handleAn array holding one handle string
Bodybody_textAn array holding one array of strings
All three, side by side
"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:

POST /api/templates
{
  "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.