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
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
Add Liquid placeholders
Send custom_data in your API request
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: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):{{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
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
- John (user123): “Hi John, you have 150 points. Your level is Gold.”
- Sarah (user456): “Hi Sarah, you have 200 points. Your level is Platinum.”
- 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.
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

Example Abandoned Cart Email Template
Create the email template
Add the layout structure
- 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

Starter email template for abandoned cart
Display the item count
Start the for-loop
- 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
- 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
{% for %} must have a matching {% endfor %}. Missing this will break email rendering.Add a cart link button

Finished base email template for abandoned cart without styling
Test the template
- Setup your API request using the Example
custom_datapayload - Send yourself the email.
- Verify the data displays correctly.
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:
Abandoned cart push template example
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
- 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
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
Create Message API
Using Liquid syntax
Templates
support@onesignal.comPlease 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