Templates API
Create, update, list, and delete message templates for your WhatsApp Business Account. Templates must be approved by Meta before they can be used to send messages.
Official Documentation: WhatsApp Templates API
Overview
The Templates API allows you to manage pre-approved message templates:
- Create Templates: Define reusable message structures with placeholders
- List Templates: Retrieve all templates or filter by name
- Update Templates: Modify existing template components
- Delete Templates: Remove templates you no longer need
- Template Status: Track approval status and quality ratings
Endpoints
GET /{WABA_ID}/message_templates?...POST /{WABA_ID}/message_templatesGET /{TEMPLATE_ID}POST /{TEMPLATE_ID}DELETE /{WABA_ID}/message_templates?...Important Notes
Quick Start
import WhatsApp from 'meta-cloud-api';
const client = new WhatsApp({ accessToken: process.env.CLOUD_API_ACCESS_TOKEN!, phoneNumberId: Number(process.env.WA_PHONE_NUMBER_ID), businessAcctId: process.env.WA_BUSINESS_ACCOUNT_ID!,});
// List templatesconst templates = await client.templates.getTemplates({ name: 'order_update', limit: 10,});
// Create a new templateconst created = await client.templates.createTemplate({ name: 'welcome_message', category: 'MARKETING', language: 'en_US', components: [ { type: 'BODY', text: 'Hi {{1}}, welcome to {{2}}! We are excited to have you.', }, ],});
// Update templateawait client.templates.updateTemplate(created.id, { components: [ { type: 'BODY', text: 'Hi {{1}}, thanks for joining {{2}}!', }, ],});
// Delete templateawait client.templates.deleteTemplate({ name: 'old_template',});Create Template
Create a new message template with header, body, footer, and buttons.
Basic Text Template
import { TemplateCategory } from 'meta-cloud-api/types';
await client.templates.createTemplate({ name: 'appointment_reminder', category: TemplateCategory.UTILITY, language: 'en_US', components: [ { type: 'BODY', text: 'Hi {{1}}, your appointment is scheduled for {{2}}.', }, ],});Template with Header
await client.templates.createTemplate({ name: 'order_confirmation', category: 'UTILITY', language: 'en_US', components: [ { type: 'HEADER', format: 'TEXT', text: 'Order Confirmed', }, { type: 'BODY', text: 'Thank you {{1}}! Your order #{{2}} has been confirmed and will be delivered by {{3}}.', }, { type: 'FOOTER', text: 'Thank you for shopping with us', }, ],});Template with Media Header
await client.templates.createTemplate({ name: 'promo_offer', category: 'MARKETING', language: 'en_US', components: [ { type: 'HEADER', format: 'IMAGE', example: { header_handle: ['https://example.com/promo-image.jpg'], }, }, { type: 'BODY', text: 'Special offer for {{1}}! Get {{2}}% off your next purchase.', example: { body_text: [['John', '20']], }, }, ],});Template with Buttons
await client.templates.createTemplate({ name: 'support_options', category: 'UTILITY', language: 'en_US', components: [ { type: 'BODY', text: 'Hi {{1}}, how can we help you today?', }, { type: 'BUTTONS', buttons: [ { type: 'QUICK_REPLY', text: 'Track Order', }, { type: 'QUICK_REPLY', text: 'Contact Support', }, { type: 'URL', text: 'Visit Website', url: 'https://example.com', }, ], }, ],});Authentication Template
await client.templates.createTemplate({ name: 'otp_verification', category: 'AUTHENTICATION', language: 'en_US', components: [ { type: 'BODY', text: 'Your verification code is {{1}}. It expires in 5 minutes.', example: { body_text: [['123456']], }, }, { type: 'BUTTONS', buttons: [ { type: 'OTP', otp_type: 'COPY_CODE', text: 'Copy Code', }, ], }, ],});List Templates
Retrieve all templates or filter by specific criteria.
Get All Templates
const allTemplates = await client.templates.getTemplates();
console.log(allTemplates.data); // Array of templatesconsole.log(allTemplates.paging); // Pagination infoFilter by Name
const templates = await client.templates.getTemplates({ name: 'order_update',});Pagination
// Get first pageconst page1 = await client.templates.getTemplates({ limit: 20,});
// Get next page using cursorif (page1.paging?.next) { const page2 = await client.templates.getTemplates({ limit: 20, after: page1.paging.cursors.after, });}Filter by Status
const templates = await client.templates.getTemplates({ status: 'APPROVED', limit: 50,});Get Template Details
Retrieve detailed information about a specific template.
const template = await client.templates.getTemplate('TEMPLATE_ID');
console.log(template);// {// id: 'TEMPLATE_ID',// name: 'order_update',// status: 'APPROVED',// category: 'UTILITY',// language: 'en_US',// components: [...],// quality_score: { score: 'GREEN', date: '...' }// }Update Template
Modify existing template components. Updates are partial - only provide fields you want to change.
await client.templates.updateTemplate('TEMPLATE_ID', { components: [ { type: 'BODY', text: 'Updated message: Hi {{1}}, your order {{2}} is ready!', }, ],});Delete Template
Remove templates you no longer need.
Delete by Name
await client.templates.deleteTemplate({ name: 'old_template',});Delete Specific Language Version
await client.templates.deleteTemplate({ name: 'welcome_message', language: 'es_ES',});Response Formats
Create/Get Template Response
{ id: '1234567890', name: 'order_update', status: 'PENDING', // PENDING, APPROVED, REJECTED, PAUSED category: 'UTILITY', language: 'en_US', components: [ { type: 'BODY', text: 'Hi {{1}}, your order is ready!', } ], quality_score: { score: 'GREEN', // GREEN, YELLOW, RED date: '2024-01-15T10:30:00Z' }}List Templates Response
{ data: [ { id: '...', name: '...', ... }, { id: '...', name: '...', ... } ], paging: { cursors: { before: 'CURSOR_STRING', after: 'CURSOR_STRING' }, next: 'https://graph.facebook.com/...' }}Error Handling
try { await client.templates.createTemplate({ name: 'my_template', category: 'UTILITY', language: 'en_US', components: [{ type: 'BODY', text: 'Hello!' }], });} catch (error) { if (error.response) { const { code, message } = error.response.data.error;
switch (code) { case 100: console.error('Template name already exists'); break; case 2388124: console.error('Template format invalid'); break; case 131000: console.error('Template content violates policies'); break; default: console.error(`Error ${code}: ${message}`); } }}Best Practices
-
Use Clear Template Names: Choose descriptive, lowercase names with underscores
// ✅ Goodname: 'appointment_reminder'name: 'order_confirmation'// ❌ Badname: 'template1'name: 'TEMP-MSG' -
Provide Examples for Variables: Help reviewers understand your template
components: [{type: 'BODY',text: 'Hi {{1}}, your order {{2}} will arrive on {{3}}.',example: {body_text: [['John', '#A123', 'Jan 15']]}}] -
Choose Appropriate Categories: Use the right category to avoid rejection
// Marketing - requires opt-incategory: 'MARKETING'// Utility - for transactional messagescategory: 'UTILITY'// Authentication - for security codescategory: 'AUTHENTICATION' -
Monitor Quality Scores: Keep templates at GREEN status
const template = await client.templates.getTemplate(templateId);if (template.quality_score.score !== 'GREEN') {console.warn('Template quality degraded:', template.quality_score);} -
Handle Different Languages: Create localized versions
// English versionawait client.templates.createTemplate({name: 'welcome',language: 'en_US',// ...});// Spanish versionawait client.templates.createTemplate({name: 'welcome',language: 'es_ES',// ...});
Template Components
Header Types
- TEXT: Plain text header
- IMAGE: Image header
- VIDEO: Video header
- DOCUMENT: Document header
Body
- Contains the main message text
- Supports up to 1024 characters
- Use
{{1}},{{2}}, etc. for variables
Footer
- Optional text below the body
- Up to 60 characters
- No variables allowed
Buttons
- QUICK_REPLY: Up to 3 quick reply buttons
- URL: Up to 2 URL buttons (static or dynamic)
- PHONE_NUMBER: Up to 1 phone number button
- OTP: Special button for authentication templates
Related Documentation
- Messages API - Send template messages
- Flows API - Create interactive experiences
- Marketing Messages - Send marketing templates
- Template Guidelines - Meta’s template policies
Source Code
View the source code on GitHub: TemplateApi.ts