template/create
Create a new WhatsApp message template for a Business Account. Newly created templates start in PENDING status and must be approved by Meta before they can be used to send messages.
template/get to poll status or set up a scheduled workflow to check. Only templates with status APPROVED can be used in message/sendTemplate.
When to Use
- Automated template provisioning: A multi-tenant SaaS onboarding workflow creates business-specific message templates (appointment reminders, order confirmations) as part of tenant setup, without requiring manual access to Meta Business Manager.
- Localisation at scale: A global campaign workflow creates copies of a base template in multiple languages (en_US, es_MX, fr_FR) by iterating through a language list and calling this node for each locale.
- Seasonal campaign preparation: A marketing operations workflow creates promotional templates ahead of campaign periods (Black Friday, year-end sales) and submits them weeks in advance to account for Meta's review window.
- Compliance-driven template management: A regulated industry workflow creates authentication templates with pre-approved OTP structures that meet Meta's AUTHENTICATION category requirements.
- Developer workflow integration: A CI/CD pipeline calls this node as part of deployment to create or update WhatsApp templates alongside application code changes.
Configuration
Connection
| Field | Required | Description |
|---|---|---|
accessToken | Required | Permanent System User access token with whatsapp_business_management permission. Store in BizFirst Credentials Manager. |
phoneNumberId | Required | Used to identify the associated WhatsApp Business Account. The API call is made against the Business Account, not the phone number directly. |
apiVersion | Optional | Meta Graph API version, e.g. v18.0. Defaults to v18.0. |
Operation
| Field | Required | Description |
|---|---|---|
name | Required | Template name. Must be lowercase letters, numbers, and underscores only — no spaces or hyphens (e.g. order_confirmation_v2). Must be unique within the business account. |
language | Required | BCP-47 language code for the template (e.g. en_US, en_GB, es_MX, fr_FR, de_DE, pt_BR). |
category | Required | Template category: AUTHENTICATION (OTP/verification messages), MARKETING (promotional messages, offers), or UTILITY (transactional: order updates, appointment reminders). |
components | Required | JSON array of component objects defining the template structure. See component schema below. |
Components Structure
Each component object has a type field. Supported types:
- HEADER — Optional. Accepts
format:TEXT,IMAGE,VIDEO, orDOCUMENT. - BODY — Required. Contains the main message text. Supports variables in
{{1}},{{2}}format. - FOOTER — Optional. Plain text footer, max 60 characters. Does not support variables.
- BUTTONS — Optional. Supports
QUICK_REPLY(up to 3),URL(up to 2), andPHONE_NUMBER(up to 1).
Sample Configuration
{
"resource": "template",
"operation": "create",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"name": "order_shipped_notification",
"language": "en_US",
"category": "UTILITY",
"components": "[{\"type\":\"HEADER\",\"format\":\"TEXT\",\"text\":\"Your Order Has Shipped\"},{\"type\":\"BODY\",\"text\":\"Hi {{1}}, your order #{{2}} has been shipped and will arrive by {{3}}. Track your delivery at: {{4}}\"},{\"type\":\"FOOTER\",\"text\":\"BizFirstAI Commerce\"},{\"type\":\"BUTTONS\",\"buttons\":[{\"type\":\"URL\",\"text\":\"Track Order\",\"url\":\"https://track.example.com/{{1}}\"}]}]"
}
Validation Errors
| Error | Cause |
|---|---|
accessToken is required | The accessToken field is empty or missing. |
phoneNumberId is required | The phoneNumberId field is empty or missing. |
name is required | The name field is empty or missing. |
language is required | The language field is empty or missing. |
category is required | The category field is empty or missing. |
components is required | The components field is empty or missing. |
invalid_json | The components value is not valid JSON. Validate your JSON structure before passing. |
132000 (Meta) | Template name already exists for this language in the business account. |
132005 (Meta) | Template body text exceeds the 1024-character maximum. |
132012 (Meta) | Template contains disallowed content (e.g. phone numbers in MARKETING templates). |
Output
Success Port
Fires after Meta accepts the template submission. The template is now in PENDING status and awaiting Meta review. Store the id to poll status later.
| Field | Type | Description |
|---|---|---|
id | string | The newly created template ID. Store this for subsequent template/get, template/update, and template/submitForReview calls. |
templateStatus | string | Initial status after creation — always "PENDING". |
status | string | "created" on success. |
errorCode | string | Empty on success. |
errorMessage | string | Empty on success. |
rawResponse | string | Full raw JSON response from Meta. |
Error Port
Fires when Meta rejects the template or the submission fails.
| Field | Type | Description |
|---|---|---|
status | string | "failed" or "error". |
errorCode | string | Meta error code or BizFirst internal code. |
errorMessage | string | Human-readable rejection reason. |
rawResponse | string | Raw API error JSON for diagnostics. |
Sample Output
{
"id": "987654321098765",
"templateStatus": "PENDING",
"status": "created",
"errorCode": null,
"errorMessage": null,
"rawResponse": "{\"id\":\"987654321098765\",\"status\":\"PENDING\"}"
}
Expression Reference
| Value | Expression | Notes |
|---|---|---|
| Template ID | {{ $output.createTemplate.id }} | Store in a database record. Use for subsequent get/update/submitForReview calls. |
| Template status | {{ $output.createTemplate.templateStatus }} | Always "PENDING" immediately after creation. Poll via template/get. |
| Operation status | {{ $output.createTemplate.status }} | "created" on success — confirms the API call succeeded. |
| Error code | {{ $output.createTemplate.errorCode }} | Non-null on error port. Check for 132000 (duplicate name) to handle gracefully. |
Node Policies & GuardRails
| Policy Area | Recommendation |
|---|---|
| Review timeline | Meta's review process takes 24–48 hours on average but can take up to 7 days. Do not create templates and immediately attempt to send — build a review-wait loop into your workflow. |
| Name uniqueness | Template names must be unique per language. Check for existing templates with template/getMany before creating to avoid 132000 errors. |
| Category selection | Choosing the wrong category is the most common rejection reason. Use UTILITY for transactional messages, MARKETING only for promotional content, and AUTHENTICATION strictly for OTP flows. |
| Variable placeholder format | Variables in BODY text must use the {{1}}, {{2}} format — numbered sequentially from 1. Named variables are not supported. |
| Credential permissions | The access token must have the whatsapp_business_management permission scope. A token with only whatsapp_business_messaging scope will fail with a 200 permissions error. |
| Error port handling | Always connect the error port and log the full rawResponse — Meta's rejection messages contain actionable guidance on why a template was rejected. |
Examples
Create a UTILITY Order Confirmation Template
{
"resource": "template",
"operation": "create",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"name": "order_confirmation_v1",
"language": "en_US",
"category": "UTILITY",
"components": "[{\"type\":\"BODY\",\"text\":\"Thank you for your order, {{1}}! Your order #{{2}} for {{3}} has been confirmed. Expected delivery: {{4}}.\"}]"
}
A new tenant onboarding workflow creates this standard order confirmation template as part of setup. After creation, a downstream node stores the returned id in the tenant settings table. A scheduled daily job polls template/get until status reaches APPROVED, then marks the template as ready in the configuration.
Create a MARKETING Template with Quick Reply Buttons
{
"resource": "template",
"operation": "create",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"name": "summer_sale_2024",
"language": "en_US",
"category": "MARKETING",
"components": "[{\"type\":\"HEADER\",\"format\":\"IMAGE\",\"example\":{\"header_handle\":[\"{{ $input.bannerMediaHandle }}\"]}},{\"type\":\"BODY\",\"text\":\"Hi {{1}}, our Summer Sale starts tomorrow! Enjoy {{2}}% off everything. Shop now before stocks run out!\"},{\"type\":\"FOOTER\",\"text\":\"Reply STOP to unsubscribe\"},{\"type\":\"BUTTONS\",\"buttons\":[{\"type\":\"QUICK_REPLY\",\"text\":\"Shop Now\"},{\"type\":\"QUICK_REPLY\",\"text\":\"View Deals\"},{\"type\":\"QUICK_REPLY\",\"text\":\"Unsubscribe\"}]}]"
}
A campaign preparation workflow creates this template 2 weeks before the sale. The image header references a banner image already uploaded via media/upload. The template ID is stored and the workflow polls for approval daily until ready.
Create a Multi-Language Template Set
// Loop node iterates over: ["en_US", "es_MX", "fr_FR", "de_DE"]
{
"resource": "template",
"operation": "create",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"name": "appointment_reminder",
"language": "{{ $loop.currentItem }}",
"category": "UTILITY",
"components": "{{ $input.componentsForLocale[$loop.currentItem] }}"
}
A localisation workflow creates the same template in 4 languages by looping through a language list. Each language's component JSON is stored in a componentsForLocale lookup object populated by a translation service upstream. All four template IDs are collected and stored for each locale's send pipeline.