Canned Messages
The canned command (aliases: cm, cms, canned-messages) mirrors the
Settings → Canned Messages page in the app. Use it to save commonly-sent
WhatsApp replies — plain text, images, videos, documents, audio, or any
advanced Meta Cloud API payload — and recall them from chat handlers or
scripts later.
Backed by /api/v1/app/canned-messages[/:id]. Every call uses your stored
oc_live_ token and is scoped to your account.
Quick start
# Save a text snippet
splashify canned create \
--name "Welcome message" \
--shortcut "/welcome" \
--description "First-touch greeting" \
--type TEXT \
--text "Hi! Thanks for reaching out — how can we help?"
# Save an image with a caption
splashify canned create \
--name "Receipt template" \
--type IMAGE \
--url https://cdn.example.com/receipt.png \
--caption "Your receipt is attached"
# See what you've got
splashify cannedMessage types
The backend stores any of these message_type values. The CLI provides
convenience flags for the first five (the ones the app dialog exposes
interactively); the rest are reachable via --payload '<json>'.
Why these shapes? They match the app dialog's
buildPayload()exactly, so payloads written from the CLI render identically in the app's preview pane and on WhatsApp itself.
--payload is validated against --type at create/update time. A shape that
no sender can render — say --type INTERACTIVE_LIST with a {"text":…} body —
is rejected up front instead of storing a record that fails when you send it.
How each type is delivered
WhatsApp offers no free-form interactive send: tappable buttons and native list pickers are only available through an approved template. So the four types below are delivered as a numbered text list the recipient replies to by number — this is the interaction floor by design, not a fallback.
For real tappable buttons, use an approved template:
splashify message template. Run canned send --dry-run to
see the exact text a payload will produce before sending it.
Command reference
splashify canned — list
splashify canned # default: every canned message
splashify canned list # alias
splashify canned list --search receipt # name/shortcut/description match
splashify canned list --type IMAGE # filter by message_type
splashify canned list --search hi --type TEXT # combined--search and --type are applied client-side (the backend returns the
full list — same as the app does).
Response shape:
{
"success": true,
"messages": [
{
"id": "uuid",
"name": "Welcome message",
"shortcut": "/welcome",
"description": "First-touch greeting",
"message_type": "TEXT",
"message_payload": {"text": {"body": "Hi! Thanks for reaching out…"}},
"is_active": true,
"created_at": "...",
"updated_at": "..."
}
]
}splashify canned <id> — show one
splashify canned <message_id>No dedicated backend endpoint — the CLI fetches the list and filters
client-side, same pattern as splashify attribute <id> and
splashify member <id>.
splashify canned create — save a new message
splashify canned create \
--name <string> \
--type <TEXT|IMAGE|VIDEO|AUDIO|DOCUMENT|…> \
[--shortcut <string>] [--description <string>] \
[convenience flags OR --payload '<json>']splashify canned update — change an existing message
splashify canned update <message_id> [--name …] [--shortcut …] \
[--description …] [--type …] \
[--text … | --url … --caption … --filename …] \
[--payload '{…}']Read-modify-write. The backend's PUT replaces every column, so the CLI loads the current record first, overlays only the flags you passed, then sends the full body. Fields you don't mention stay intact.
# Just rename it
splashify canned update <id> --name "New welcome message"
# Switch the body of a TEXT canned message
splashify canned update <id> --text "Hi there 👋 — what can I do for you?"
# Add a caption to an existing IMAGE canned message
splashify canned update <id> --caption "Updated caption"
# Change a TEXT canned message to a VIDEO (must also pass --url)
splashify canned update <id> --type VIDEO --url https://cdn.example.com/intro.mp4
# Replace the whole payload (advanced types)
splashify canned update <id> --type INTERACTIVE_LIST \
--payload '{"interactive":{"type":"list","body":{"text":"Choose one"}, …}}'splashify canned send — send a stored message
splashify canned send <message_id> --to +919876543210
splashify canned send /welcome --to +919876543210 # by shortcut
splashify canned send /menu --to +919876543210 --dry-runResolves the message by message_id or shortcut (with or without the
leading /), picks the endpoint that can carry its type, and sends it. The
routing table is in How each type is delivered.
--dry-run prints the resolved endpoint and request body — including the
flattened text for interactive types — without sending anything:
splashify canned send /menu --dry-run{
"endpoint": "/app/messages/send-text",
"body": {
"to": "",
"text": "*Support*\n\nHow can we help you today?\n\n1. Invoices — Download past invoices\n2. Make a payment\n\nReply with the number of your choice."
}
}If a stored payload can't be rendered, the command names the offending record and sends nothing rather than putting a malformed message in front of a customer:
error: canned message "List menu" (INTERACTIVE_LIST) can't be sent: this
interactive canned message has no text, options, or link to sendsplashify canned toggle — activate / deactivate
splashify canned toggle <message_id>Flips is_active. The endpoint always returns the new state — is_active: true means the message is now usable by chat handlers, false hides it.
splashify canned delete — remove
splashify canned delete <message_id>
splashify canned rm <message_id> # alias
splashify canned remove <message_id> # aliasPermanent. There is no soft-delete and no recovery — back up first if you need to keep a copy.
Advanced — interactive payloads
For the message types the app dialog does not expose (interactive buttons,
lists, CTA URLs, location, contact, address) use --payload:
splashify canned create \
--name "Quick reply buttons" \
--type INTERACTIVE_BUTTON \
--payload '{
"interactive": {
"type": "button",
"body": {"text": "How can we help?"},
"action": {
"buttons": [
{"type": "reply", "reply": {"id": "billing", "title": "Billing"}},
{"type": "reply", "reply": {"id": "support", "title": "Support"}},
{"type": "reply", "reply": {"id": "sales", "title": "Sales"}}
]
}
}
}'
splashify canned create \
--name "Open store CTA" \
--type INTERACTIVE_CTA_URL \
--payload '{
"interactive": {
"type": "cta_url",
"body": {"text": "Open our online store"},
"action": {
"name": "cta_url",
"parameters": {"display_text": "Open store", "url": "https://shop.example.com"}
}
}
}'
splashify canned create \
--name "Office location" \
--type LOCATION \
--payload '{
"location": {
"latitude": 12.9716,
"longitude": 77.5946,
"name": "HQ",
"address": "Bangalore, India"
}
}'message_payload is stored in the shape the
WhatsApp Cloud API
uses, but it is not forwarded to Meta verbatim on send — there is no canned
"send this payload as-is" endpoint. canned send maps each type onto the
matching /app/messages/send-* endpoint, and the interactive types are
flattened to a numbered text list (see
How each type is delivered). Storing a richer
interactive payload than WhatsApp can deliver free-form is fine — it just
arrives as the numbered list. Use --dry-run to see exactly what will go out.
Common workflows
Bulk-create from a JSON file
// canned.json
[
{"name": "Hi", "type": "TEXT", "text": "Hi! How can we help?"},
{"name": "Hours", "type": "TEXT", "text": "We're open 9-6, Mon-Sat."},
{"name": "Brochure", "type": "DOCUMENT", "url": "https://…/brochure.pdf", "filename": "brochure.pdf"}
]jq -c '.[]' canned.json | while read -r row; do
splashify canned create \
--name "$(echo "$row" | jq -r '.name')" \
--type "$(echo "$row" | jq -r '.type')" \
$(echo "$row" | jq -r 'select(.text) | "--text \"\(.text)\""') \
$(echo "$row" | jq -r 'select(.url) | "--url \(.url)"') \
$(echo "$row" | jq -r 'select(.filename) | "--filename \(.filename)"')
doneDeactivate every TEXT canned message you used last quarter
splashify canned list --type TEXT | \
jq -r '.messages[] | select(.updated_at < "2026-04-01") | .id' | \
xargs -I{} splashify canned toggle {}Find the message backing a /welcome shortcut
splashify canned list --search /welcome | \
jq '.messages[] | select(.shortcut == "/welcome") | {id, name, message_type}'Round-trip an existing payload — copy with a new name
SRC_ID="<existing message_id>"
PAYLOAD=$(splashify canned $SRC_ID | jq -c '.message_payload')
TYPE=$(splashify canned $SRC_ID | jq -r '.message_type')
splashify canned create --name "Welcome — variant B" --type "$TYPE" --payload "$PAYLOAD"Re-upload media + swap the link on an existing canned message
NEW_URL=$(splashify media upload ./receipt-v2.png | jq -r '.file_url')
splashify canned update <id> --url "$NEW_URL" --caption "Updated receipt"Troubleshooting
"A canned message with this name already exists" — names are unique
case-insensitively across your account. Use splashify canned list --search <name> to find the duplicate, then either rename or delete the old row.
"Name must be 200 characters or less" — trim the --name argument.
"Invalid message payload" — your --payload JSON didn't parse on the
backend. Validate locally with jq first: echo "$PAYLOAD" | jq .
Update didn't change the type — make sure you passed --type and the
matching media flag. Switching from TEXT → IMAGE without --url would fail
validation; the CLI catches this before the round-trip and prints a usage
error.
--text ignored on an IMAGE — --text only builds the payload for
--type TEXT. For IMAGE/VIDEO/DOCUMENT captions, use --caption instead.
See also
splashify media— upload files first to get the CDN URLs you pass to--url.splashify templates— approved WhatsApp templates, which are different from canned messages (templates need Meta approval, canned messages don't).splashify message— the send-side commands that can include the payloads you stored here.