custom_data field in the Create Message API to send dynamic data from your backend and render it inside templates using Liquid syntax.
custom_data:
- Is message-specific
- Is not stored
- Exists only during the API request
- Must be used with a
template_id
Liquid
When to use custom_data
Use custom_data when you need:
- Data changes per message (order totals, cart items, balances)
- You need arrays (product lists, line items, recommendations)
- Data should not persist (one-time codes, temporary URLs)
- You send backend-triggered messages
- You want bulk personalization in one API request
How custom_data personalization works
Adding custom_data to messages requires a few steps:
Create a template
Create a Push, Email, or SMS Template in the dashboard or via Create Template API.
Send custom_data in your API request
Call the Create Message API with:
template_id- The ID of the templatecustom_data- The data object- Audience targeting (
include_player_ids,include_aliases, or segments)
Data patterns
Common examples of data patterns you can use withcustom_data.
Flat JSON example
Use simple key-value pairs for basic personalization like names, IDs, URLs, or any single-value data. Use case: Transactional messages (invoices, receipts, confirmations) where each field contains a single value. Template:Liquid
JSON
Text
Array data example
Pass arrays of objects to work with multiple items like cart products, order line items, or recommendations. Arrays enable both direct access (indexing) and iteration (loops). Use case: Displaying product lists, leaderboards, order summaries, or any multi-item data. Indexing template (accessing first item):Liquid
Liquid
JSON
Text
{{message.custom_data.cart_items.size}}— Number of items in array (returns2in this example){{message.custom_data.cart_items.first.item_name}}— First item’s name (equivalent to[0]){{message.custom_data.cart_items.last.item_name}}— Last item’s name
Bulk personalization example
Send a single API request to multiple users where each recipient sees personalized content based on theirexternal_id.
How it works:
- Structure
custom_dataas an object where keys are external_ids and values are user-specific data - In the template, use
subscription.external_idto look up the current recipient’s data - OneSignal renders the template once per recipient with their specific data
Liquid
subscription.external_idcontains the current recipient’s external_id (e.g., “user123”)message.custom_data.users[subscription.external_id]looks up that user’s data from the custom_data objectuserbecomes a shorthand variable for that user’s data- Each recipient only sees their own personalized content
JSON
- John (user123): “Hi John, you have 150 points. Your level is Gold.”
- Sarah (user456): “Hi Sarah, you have 200 points. Your level is Platinum.”
Requirements for bulk personalization:
- All recipients must have an
external_idset in OneSignal - Each
external_idininclude_aliasesmust have a matching key incustom_data.users - If a recipient’s external_id is missing from
custom_data, their message will have empty fields
Example: Abandoned cart with custom_data
How to build abandoned cart messages for both email and push usingcustom_data.
When to use this approach:
- Your server detects cart abandonment (e.g., 1 hour after last activity)
- Real-time cart data is in your database
- You want to display multiple products with images, names, prices
- Each user may have different items and quantities
- You want to orchestrate the message from your backend.
Example custom_data payload
This is the Create Message API request for this example.
JSON
| Field | Type | Purpose |
|---|---|---|
cart_url | string | Customer’s unique cart link (for buttons/launch URLs) |
cart | array | List of products—supports counting, looping, and detail display |
product_image | string | Product image (per item in array) |
product_name | string | Product name (per item) |
product_quantity | string | Quantity (per item) |
product_price | string | Price with formatting (per item) |
You can name fields anything you want—just ensure your template’s Liquid syntax matches.
Email template
This example shows how to build an email template that displays:- The cart item count
- Each product with image, name, quantity, and price using a for-loop
- A button that links to the customer’s unique cart URL

Create the email template
Navigate to Messages > Templates > New Email Template and open the Drag & Drop Editor.
Add the layout structure
Create five rows:
- Rows 1, 2, and 4: one column with a Paragraph block
- Row 3: four columns with HTML | Paragraph | Paragraph | Paragraph
- Row 5: one column with a Button block

Display the item count
In row 1, add:For better grammar, you could use a conditional to say “1 item” vs “2 items”, but for abandoned cart emails, plural is usually acceptable.
Liquid
Liquid
Start the for-loop
Use a for-loop to repeat the product display row for each cart item.In row 2 (loop start), add:What this does:
Text
- Begins a loop that iterates over each object in the
cartarray - Creates a temporary variable called
productthat represents the current item - Everything between
{% for %}and{% endfor %}repeats once per cart item - You can name
productanything (e.g.,item,cartItem)—just stay consistent
Display product details
This 4-column row shows image, name, quantity, and price. Because it’s inside the loop, it repeats for every cart item.In row 3 (product details), configure:Column 1 - HTML block (product image):Columns 2–4 - Text blocks (product name, quantity, price):
HTML
- Column 2:
{{product.product_name}} - Column 3:
{{product.product_quantity}} - Column 4:
{{product.product_price}}
- On the first iteration,
product= first object in the cart array {{product.product_image}}gets the first item’s image URL- On the second iteration,
product= second object - Row repeats automatically for all cart items
End the for-loop
Close the loop to mark where repetition stops.In row 4 (loop end), add:
Liquid
Every
{% for %} must have a matching {% endfor %}. Missing this will break email rendering.Test the template
- Setup your API request using the Example
custom_datapayload - Send yourself the email.
- Verify the data displays correctly.
Success! Now you can apply your own styling to the template. See Design emails with drag-and-drop.
Push template
Push notifications have character limits and operating system restrictions, so instead of showing all items, display the first product and indicate the total count with proper grammar. Here is an example push notification we will build:
Liquid
See Using Liquid syntax for more information.
Liquid
See Notification images & rich media for more information.
Liquid
Success! Save the template and use its
template_id in your Create message API request with the custom_data property to test.Troubleshooting & best practices
- Keep it simple: Only include data you’ll actually use in the template
- Stay under 2KB: Monitor your payload size, especially with arrays
- Use consistent naming: Stick to
snake_caseorcamelCasethroughout - Validate before sending: Check for null values, empty arrays, and required fields
- Always use default filters for optional fields:
Liquid
- Check array size before looping:
Liquid
- Test with edge cases: empty arrays, missing fields, maximum item counts
- Log API responses server-side to catch validation errors
- Monitor message delivery rates—sudden drops may indicate Liquid errors
- Keep fallback templates ready for critical transactional messages
- Pre-format complex data in your backend rather than using complex Liquid logic
- Cache templates and reuse them across many API calls
- Consider separating high-volume transactional messages from marketing campaigns
Message sends but content is blank
Message sends but content is blank
Cause: Liquid syntax errors or mismatched field namesSolutions:
- Verify field names match exactly between
custom_dataand template (case-sensitive) - Check for typos:
{{message.custom_data.name}}not{{message.custm_data.name}} - Use default filters to catch missing fields
- Test templates with the actual
custom_datastructure before production
API Error: Request body too large
API Error: Request body too large
Cause:
custom_data exceeds 2KB limitSolutions:- Remove unnecessary fields from your payload
- Shorten field names and values where possible
- Limit arrays to first 3-5 items
- Move large static content (like full HTML) to your template instead
Related pages
Message personalization
Overview of all personalization options in OneSignal, including Data Tags, user attributes, and segmentation.
Create Message API
Complete API reference for sending messages with custom_data, targeting options, and all available fields.
Using Liquid syntax
Full Liquid syntax reference including filters, conditionals, loops, and string manipulation.
Templates
Create and manage reusable message templates for Push, Email, SMS, and In-App channels.
Need help?Chat with our Support team or email
[email protected]Please include:- Details of the issue you’re experiencing and steps to reproduce if available
- Your OneSignal App ID
- The External ID or Subscription ID if applicable
- The URL to the message you tested in the OneSignal Dashboard if applicable
- Any relevant logs or error messages
