Templates
The templates / template commands manage WhatsApp templates (mirroring
/templates and /templates/create), and the rcs templates / rcs template commands manage RCS templates (mirroring /templates/rcs/create).
Both surfaces share the same lifecycle:
You write a template ────► Submit to Meta / JioCX ────► Approval ────► Send to users
↑ (CLI handles this) ↑ (out-of-band)The CLI handles the submit step and the read-side lifecycle (list,
inspect, sync the status, delete). Approval is decided by Meta (WhatsApp)
or JioCX (RCS) and surfaces in the status field — you poll it via the
sync / check-status commands.
Backed by /api/v1/app/templates/* and /api/v1/app/rcs/templates/*.
Quick start — WhatsApp
# Simplest template: text-only marketing message
splashify templates create \
--name "promo_welcome" \
--language en \
--category MARKETING \
--text "Welcome to our store! Use code WELCOME10 for 10% off your first order."
# See it (status will be PENDING until Meta reviews it)
splashify templates # list
splashify template <template_id> # show one
# Poll for the latest approval status
splashify templates sync # sync ALL from Meta
splashify templates sync <template_id> # sync just oneQuick start — RCS
# Simplest: basic text RCS template
splashify rcs templates create \
--name "rcs_promo_welcome" \
--type basic \
--text "Welcome! Tap to shop our latest drop."
splashify rcs templates # list
splashify rcs template <template_id> # show one
splashify rcs template <template_id> check-status # poll approval stateWhatsApp template commands
splashify templates — list
splashify templates # default
splashify templates list # alias| Backed by | GET /api/v1/app/templates |
|---|
Response shape (per template):
{
"template_id": "1234567890123456",
"waba_id": "11122233344",
"name": "promo_welcome",
"status": "APPROVED",
"category": "MARKETING",
"language": "en",
"quality_rating": "GREEN",
"components": [/* Meta components array */],
"synced_at": "2026-05-21T09:14:22Z",
"created_at": "2026-05-20T08:00:00Z"
}status will be one of APPROVED, PENDING, REJECTED, IN_APPEAL, or
DISABLED — same values Meta uses.
splashify template <template_id> — show one
splashify template 1234567890123456No dedicated backend GET — the CLI fetches the list and filters
client-side. Same pattern as splashify member <id>, splashify canned <id>, etc.
splashify templates sync — pull from Meta
splashify templates sync # sync ALL templates (alias: refresh)
splashify templates sync 1234567890123456 # sync just one template| Backed by | POST /api/v1/app/templates/sync |
|---|---|
| Or | POST /api/v1/app/templates/:template_id/sync |
This is the canonical way to refresh template status (APPROVED /
REJECTED / etc.) after Meta reviews it. Run it after a create, or on
a cron to keep your local mirror in sync.
splashify templates upload-media <file> — get a media handle
For templates that have a media header (IMAGE / VIDEO / DOCUMENT), Meta
requires you to upload the sample file first and reference its media
handle in the components array. The CLI exposes this as a
two-step flow:
# 1. Upload — get back a handle
HANDLE=$(splashify templates upload-media ./hero.jpg | jq -r '.handle')
echo "$HANDLE"
# "4::aW1hZ2UvanBlZw==:..." (opaque)
# 2. Plug it into example.header_handle in your components
cat <<EOF > ./tpl-with-media.json
{
"name": "spring_promo",
"language": "en",
"category": "MARKETING",
"components": [
{
"type": "HEADER",
"format": "IMAGE",
"example": { "header_handle": ["$HANDLE"] }
},
{
"type": "BODY",
"text": "Hi {{1}}, check out our spring drop ↓"
}
]
}
EOF
splashify templates create --file ./tpl-with-media.json| Backed by | POST /api/v1/app/templates/media-handle (multipart) |
|---|
The handle is persistent across templates for the lifetime Meta keeps it — you can reference one upload from multiple templates without re-uploading.
splashify templates create — submit a new template
Three ways to supply the body — pick whichever matches your shape.
# (1) Convenience for a TEXT-only template
splashify templates create \
--name "promo_welcome" --language en --category MARKETING \
--text "Welcome to our store!"
# (2) Inline components JSON
splashify templates create \
--name "promo_welcome" --language en --category MARKETING \
--components '[
{"type":"BODY","text":"Hi {{1}}, your order {{2}} has shipped."},
{"type":"FOOTER","text":"Reply STOP to opt out"}
]'
# (3) Full payload from disk — name/language/category live inside the file
splashify templates create --file ./tpl-with-media.json| Backed by | POST /api/v1/app/templates |
|---|
| Flag | Required | Notes |
|---|---|---|
--name | yes | Snake_case recommended; Meta lowercases. Max 512 chars |
--language | yes | BCP-47 code: en, en_US, hi, es, … |
--category | yes | MARKETING | UTILITY | AUTHENTICATION |
--text | one of | Body text — emits a single BODY component |
--components | one of | Explicit Meta components array (JSON) |
--file | one of | Path to JSON with either a components array OR a full {name,language,category,components} payload |
Components reference
A template is a list of Meta components. The full grammar (from the Meta docs ):
[
{
"type": "HEADER",
"format": "TEXT|IMAGE|VIDEO|DOCUMENT|LOCATION",
"text": "…", // when format=TEXT
"example": {
"header_handle": ["<handle>"] // when format=IMAGE/VIDEO/DOCUMENT
}
},
{
"type": "BODY",
"text": "Hi {{1}}, your order {{2}} has shipped",
"example": { "body_text": [["Alice", "ORD-1024"]] }
},
{
"type": "FOOTER",
"text": "Reply STOP to opt out"
},
{
"type": "BUTTONS",
"buttons": [
{"type": "QUICK_REPLY", "text": "Track order"},
{"type": "URL", "text": "Visit site", "url": "https://example.com/"},
{"type": "PHONE_NUMBER", "text": "Call us", "phone_number": "+919876543210"}
]
}
]Other supported component types: CAROUSEL (with nested cards[] →
each card has its own HEADER/BODY/BUTTONS), LIMITED_TIME_OFFER (LTO),
AUTHENTICATION body (with add_security_recommendation and
code_expiration_minutes).
Variables.
{{1}},{{2}}, … are positional placeholders Meta fills in at send time. Each variable requires a sample value inexample.body_textfor Meta’s review.
splashify templates delete <template_id> — remove
splashify templates delete 1234567890123456
splashify templates delete 1234567890123456 --name "promo_welcome" # safer| Backed by | DELETE /api/v1/app/templates/:template_id[?name=…] |
|---|
Optionally pass --name so Meta can target the deletion accurately by
name — recommended for templates with multiple language variants under
the same name.
RCS template commands
RCS has its own provider stack (JioCX for India) and its own template
shape. The rcs templates subtree namespaces these to keep them
distinct from WhatsApp’s.
Feature gate. RCS templates require the RCS feature to be enabled on your account. If not, the create endpoint returns HTTP 403 with
RCS is not enabled for your account— contact your provider to enable it.
splashify rcs templates — list
splashify rcs templates # default
splashify rcs templates list # alias
splashify rcs template <id> # show one| Backed by | GET /api/v1/app/rcs/templates |
|---|
Response shape (per template):
{
"template_id": "uuid",
"name": "rcs_promo_welcome",
"template_type": "rich_card", // basic | rich_card | carousel
"status": "approved", // pending | ai_reviewing | approved | rejected
"template_data": "{\"type\":\"card\",\"card\":{…}}",
"rejection_reason": "",
"created_at": "...",
"updated_at": "..."
}
template_datais stored as a JSON-encoded string — usejq -r '.template_data | fromjson'to expand it.
splashify rcs template <id> check-status — poll approval
splashify rcs template <template_id> check-status
splashify rcs template <template_id> status # alias| Backed by | GET /api/v1/app/rcs/templates/:template_id/check-status |
|---|
Returns the latest status from the underlying RCS provider. Run it
after a create to confirm pending → approved.
splashify rcs templates upload-media — upload rich-card media
splashify rcs templates upload-media ./hero.jpg --height MEDIUM
splashify rcs templates upload-media ./tall.jpg --height TALL
splashify rcs templates upload-media ./short.jpg --height SHORT| Backed by | POST /api/v1/app/rcs/templates/upload-media (multipart with media_height field) |
|---|
Returns a URL you plug into a rich_card’s file_url (and optionally
thumbnail_url).
--height | Media slot proportions |
|---|---|
SHORT | aspect ratio ~16:9, ~108dp |
MEDIUM | ~16:9, ~168dp (default) |
TALL | ~16:9, ~264dp |
splashify rcs templates create — submit
Three ways to supply template_data:
# (1) Convenience — basic text RCS message
splashify rcs templates create \
--name "rcs_promo_welcome" \
--type basic \
--text "Welcome! Tap to shop our latest drop."
# (2) Inline JSON for a rich card or carousel
splashify rcs templates create \
--name "rcs_card" \
--type rich_card \
--data '{
"type": "card",
"card": {
"title": "Spring drop",
"description": "20% off all jackets — this week only",
"url": "<file_url from upload-media>",
"suggestions": [
{"type": "url", "text": "Shop now", "url": "https://example.com/spring", "postback": "shop_now"},
{"type": "message", "text": "Notify me", "postback": "notify_me"}
]
}
}'
# (3) Full payload from disk
splashify rcs templates create --name "rcs_carousel" --type carousel --file ./carousel.json| Backed by | POST /api/v1/app/rcs/templates |
|---|
| Flag | Required | Notes |
|---|---|---|
--name | yes | Display name |
--type | yes | basic | rich_card | carousel |
--text | one of | Body text (basic only) — emits {"type":"text","text":"…"} |
--data | one of | Inline JSON template_data |
--file | one of | Path to a JSON file with template_data |
template_data shapes
The shapes the app dialog builds (see
buildTemplateData):
// type: basic
{
"type": "text",
"text": "Hi! Tap to shop ↓",
"suggestions": [/* optional */]
}
// type: rich_card
{
"type": "card",
"card": {
"title": "Spring drop",
"description": "20% off…",
"url": "<media file_url>",
"thumbnail_url": "<optional>",
"suggestions": [/* optional */]
}
}
// type: carousel
{
"type": "multiple_cards",
"cards": [
{ "card": { /* same shape as rich_card.card */ } },
{ "card": { /* … */ } }
]
}Suggestion shapes
Five suggestion types are supported (mapped from the page’s form to RML on submit):
// reply → quick-reply chip
{"type": "message", "text": "Notify me", "postback": "notify_me"}
// open URL
{"type": "url", "text": "Shop now", "url": "https://example.com/", "postback": "shop_now"}
// dialer
{"type": "dial", "text": "Call us", "call_to": "+919876543210", "postback": "call_us"}
// calendar event
{
"type": "calendar", "text": "Add to calendar", "postback": "cal",
"title": "Brand launch", "description": "RSVP early",
"start": "2026-06-01T10:00:00Z",
"end": "2026-06-01T12:00:00Z"
}
// location
{
"type": "location", "text": "Find a store", "postback": "loc",
"latitude": 12.9716, "longitude": 77.5946
}splashify rcs templates delete
splashify rcs templates delete <template_id>| Backed by | DELETE /api/v1/app/rcs/templates/:template_id |
|---|
Common workflows
Bulk-sync once a day via cron
# crontab — every night at 03:30
30 3 * * * /usr/local/bin/splashify templates sync >/var/log/splashify-tpl-sync.log 2>&1The endpoint is idempotent. Each run upserts the local mirror with Meta’s authoritative status, so a rejected template visibly flips within ~24h.
Submit a template and wait for approval
# Submit
RESP=$(splashify templates create --name "promo" --language en --category MARKETING --text "Hi!")
TID=$(echo "$RESP" | jq -r '.template_id // .id')
# Poll every 30s for up to 10 minutes
for _ in $(seq 1 20); do
sleep 30
splashify templates sync "$TID" > /dev/null
STATUS=$(splashify template "$TID" | jq -r '.status')
echo "$STATUS"
[ "$STATUS" = "APPROVED" ] && break
[ "$STATUS" = "REJECTED" ] && break
doneFind every REJECTED template + reason
splashify templates | jq '.templates[] | select(.status == "REJECTED") |
{template_id, name, language, quality_rating}'Export a template’s components as a re-usable JSON file
splashify template <id> | jq '{name, language, category, components}' > export.json
# Submit it to another WABA (after you switch your CLI's context) with:
splashify templates create --file ./export.jsonCarousel via JSON file (WhatsApp)
// ./carousel.json
{
"name": "spring_carousel",
"language": "en",
"category": "MARKETING",
"components": [
{ "type": "BODY", "text": "Spring drop — pick your style ↓" },
{
"type": "CAROUSEL",
"cards": [
{
"components": [
{ "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["<handle-1>"] } },
{ "type": "BODY", "text": "Jackets — 20% off" },
{ "type": "BUTTONS","buttons": [{"type":"URL","text":"Shop","url":"https://example.com/jackets"}]}
]
},
{
"components": [
{ "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["<handle-2>"] } },
{ "type": "BODY", "text": "Tees — 15% off" },
{ "type": "BUTTONS","buttons": [{"type":"URL","text":"Shop","url":"https://example.com/tees"}]}
]
}
]
}
]
}splashify templates create --file ./carousel.jsonCarousel via JSON file (RCS)
// ./rcs-carousel.json
{
"type": "multiple_cards",
"cards": [
{
"card": {
"title": "Jackets", "description": "20% off",
"url": "<file_url-1>",
"suggestions": [
{"type": "url", "text": "Shop", "url": "https://example.com/jackets", "postback": "shop_jackets"}
]
}
},
{
"card": {
"title": "Tees", "description": "15% off",
"url": "<file_url-2>",
"suggestions": [
{"type": "url", "text": "Shop", "url": "https://example.com/tees", "postback": "shop_tees"}
]
}
}
]
}splashify rcs templates create --name "rcs_spring_carousel" --type carousel --file ./rcs-carousel.jsonTroubleshooting
“No WABA connected to your account yet” — finish the Meta Embedded
Signup flow at app.splashifypro.com → WhatsApp → Connect Number, then
retry. splashify waba confirms the connection.
templates create returns Meta’s error_user_msg — Meta refused
the template. The most common reasons: missing variables in the body
({{1}} etc. without example.body_text), category mismatch
(promotional language in a UTILITY template), or restricted content.
The CLI surfaces Meta’s error_user_msg directly so the message is
actionable.
Status stuck at PENDING — Meta reviews can take up to 24h
(usually < 2 minutes). Run splashify templates sync <template_id> to
force-refresh; if it’s still PENDING after an hour, Meta is backed
up — wait.
upload-media returns an opaque error — file too large (Meta caps
templates at 5MB image / 16MB video / 100MB document), unsupported
format (JPG/PNG/MP4/PDF/DOCX/XLSX), or the file path is wrong. Try a
smaller / simpler file first.
RCS
“RCS is not enabled for your account” — RCS is a separately-billed
feature. Check splashify subscription and contact your provider to
add the RCS add-on.
Status ai_reviewing — the provider runs an AI policy check
first. It usually resolves to approved or rejected within seconds.
rcs template <id> check-status re-polls.
rejected with rejection_reason — read the reason field for the
specific guideline that was violated. Fix the template_data and
re-create (RCS templates cannot be edited in place; create a new one).
Carousel only has 1 card — RCS requires ≥ 2 cards in a carousel. The page enforces this client-side; the backend may also reject it.
See also
splashify waba— the prerequisite WABA connection.splashify message template— send an approved WhatsApp template.splashify broadcast create— use an approved template in a bulk send.splashify media— the general-purpose media library (separate from template media handles, which are Meta-side artefacts).