Skip to content

Product recommendation banners in code editor

The code editor lets you build dynamic product recommendation banners using HTML, CSS, JavaScript, and Razor syntax. You can use them to display personalized product recommendations in emails and on your website.

Live preview requirements for banners

The editor preview won’t render actual product data – variables will appear as raw expressions like @product.Name.

To see real data, use the Live preview option or test the banner on your actual website.

Supported technologies

The code editor supports:

  • HTML5 – for structuring your banner content.
  • CSS3 – for styling and responsive design.
  • JavaScript – for interactive functionality and dynamic behavior.
  • Razor Syntax – for server-side logic and data binding using @{}blocks.

Initializing product recommendations#

To retrieve product recommendations, use the following Razor syntax:

@{
    var recommendations = Model.GetRecommendations(4);
}

This retrieves up to 4 recommended products. You can adjust the number to match your banner layout.

The method returns an object with two properties:

  • recommendations.Products – the list of recommended products.
  • recommendations.Currency – the store currency, used to display prices.

Available product variables

The following properties are available for each product in recommendations.Products:

PropertyTypeDescriptionExample usage
IdstringUnique product identifier.product.Id
NamestringProduct name.product.Name
ImageUrlstringURL of the product image.product.ImageUrl
UrlstringURL of the product page.product.Url
CategorystringProduct category.product.Category
PriceStringstringRegular price, formatted as string.product.PriceString
CurrentPriceStringstringSale price, formatted as string. Empty if no sale is active.product.CurrentPriceString
OmnibusPriceStringstringLowest price in the last 30 days (EU Omnibus Directive).product.OmnibusPriceString
AdditionalInfostringOptional additional information.product.AdditionalInfo
CustomProductAttributeslistCustom attributes defined for the product (e.g. Brand, Color).See usage examples below.

The store currency is available as a separate object:

PropertyTypeDescriptionExample usage
recommendations.Currency.SignstringCurrency symbol (e.g. €, $, zł).@recommendations.Currency.Sign
recommendations.Currency.ISOstringISO currency code (e.g. EUR, USD, PLN).@recommendations.Currency.ISO

Common use cases#

Checking if a product exists

Always check for null before accessing product properties:

@{
    var recommendations = Model.GetRecommendations(4);
    var product = recommendations.Products?.FirstOrDefault();
}
@if (product != null)
{
    @* Display your product here *@
}

Displaying the sale price with a fallback

Use CurrentPriceString for the sale price. If it’s empty, fall back to PriceString:

@(string.IsNullOrWhiteSpace(product.CurrentPriceString)
    ? product.PriceString
    : product.CurrentPriceString)
@recommendations.Currency.Sign

Displaying Omnibus price

The omnibus price is required for EU compliance when showing discounted prices:

@if (!string.IsNullOrWhiteSpace(product.OmnibusPriceString))
{
    <div>Lowest price in 30 days: @product.OmnibusPriceString @recommendations.Currency.Sign</div>
}

Calculating and displaying the discount percentage

To calculate and display the discount as a percentage:

@{
    decimal regularPrice = 0;
    decimal salePrice = 0;
    decimal discountPercentage = 0;

    if (decimal.TryParse(product.PriceString, out regularPrice) &&
        decimal.TryParse(product.CurrentPriceString, out salePrice) &&
        regularPrice > 0 && salePrice > 0)
    {
        discountPercentage = Math.Round(((regularPrice - salePrice) / regularPrice) * 100, 0);
    }
}
@if (discountPercentage > 0)
{
    <div>-@discountPercentage%</div>
}

Complete price display logic

This example handles all price scenarios – regular price, sale price, and omnibus price:

@if (!string.IsNullOrWhiteSpace(products.CurrentPriceString))
{
    <div>@products.CurrentPriceString @recommendations.Currency.Sign</div>
    <div><s>@products.PriceString @recommendations.Currency.Sign</s></div>

    @if (!string.IsNullOrWhiteSpace(products.OmnibusPriceString))
    {
        <div>Lowest price in 30 days: @products.OmnibusPriceString @recommendations.Currency.Sign</div>
    }
}
else
{
    <div>@products.PriceString @recommendations.Currency.Sign</div>
}

Looping through all products

Here’s how to loop through all retrieved products and display their properties:

@{
    var recommendations = Model.GetRecommendations(4);
}
@foreach (var product in recommendations.Products)
{
    if (product != null)
    {
        <div>@product.Id</div>
        <div>@product.Name</div>
        <img src="@product.ImageUrl" alt="@product.Name" />
        <a href="@product.Url">@product.Url</a>
        <div>@product.PriceString @recommendations.Currency.Sign</div>
        <div>@product.CurrentPriceString @recommendations.Currency.Sign</div>
        <div>@product.OmnibusPriceString @recommendations.Currency.Sign</div>
    }
}

Reading a custom product attribute

@{
    var brand = product?.CustomProductAttributes
        ?.FirstOrDefault(x => x.Name == "Brand")?.ValueString;
}

Sample banner

Here’s a complete example of a responsive 4-product recommendation banner:

<style>
    .banner-header {
        text-align: center;
        padding: 20px;
        background: white;
    }

    .banner-header h1 {
        color: #2c3e50;
        font-size: 24px;
        margin-bottom: 5px;
        font-weight: 600;
    }

    .banner-header p {
        color: #7f8c8d;
        font-size: 14px;
        margin: 0;
    }

    .expertsender-banner {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
        gap: 0;
        width: 100%;
        max-width: 1200px;
        margin: 0 auto;
        background: white;
        box-sizing: border-box;
    }

    .banner-title {
        grid-column: 1 / -1;
        text-align: center;
        font-size: 32px;
        color: #2c3e50;
        margin: 40px 0 30px 0;
        font-weight: 300;
        letter-spacing: -0.5px;
    }

    .product-card {
        background: white;
        padding: 30px 20px;
        text-align: center;
        transition: all 0.3s ease;
        position: relative;
        display: flex;
        flex-direction: column;
        height: 520px;
        border-right: 1px solid #f1f3f4;
    }

    .product-card:last-child {
        border-right: none;
    }

    .product-card:hover {
        background: #fafbfc;
        transform: translateY(-2px);
    }

    .discount-badge {
        position: absolute;
        top: 20px;
        right: 20px;
        background: #e74c3c;
        color: white;
        padding: 6px 12px;
        border-radius: 20px;
        font-size: 12px;
        font-weight: 600;
        box-shadow: 0 2px 8px rgba(231, 76, 60, 0.25);
    }

    .product-image {
        width: 220px;
        height: 220px;
        object-fit: cover;
        border-radius: 8px;
        margin: 0 auto 20px auto;
        transition: transform 0.3s ease;
        background: #f8f9fa;
    }

    .product-card:hover .product-image {
        transform: scale(1.02);
    }

    .product-name {
        font-size: 18px;
        font-weight: 500;
        color: #2c3e50;
        margin-bottom: 15px;
        height: 50px;
        overflow: hidden;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        line-height: 1.4;
    }

    .price-section {
        margin-bottom: 25px;
        flex-grow: 1;
        display: flex;
        flex-direction: column;
        justify-content: center;
    }

    .current-price {
        font-size: 24px;
        font-weight: 600;
        color: #2c3e50;
        margin-bottom: 5px;
    }

    .sale-price {
        font-size: 24px;
        font-weight: 600;
        color: #e74c3c;
        margin-bottom: 5px;
    }

    .original-price {
        font-size: 16px;
        text-decoration: line-through;
        color: #95a5a6;
    }

    .buy-button {
        background: #2c3e50;
        color: white;
        padding: 12px 32px;
        border: none;
        border-radius: 4px;
        text-decoration: none;
        display: inline-block;
        font-size: 14px;
        font-weight: 500;
        transition: all 0.3s ease;
        text-transform: uppercase;
        letter-spacing: 1px;
        margin-top: auto;
    }

    .buy-button:hover {
        background: #34495e;
        transform: translateY(-1px);
        box-shadow: 0 4px 12px rgba(44, 62, 80, 0.25);
    }

    @media (max-width: 768px) {
        .product-card {
            border-right: none;
            border-bottom: 1px solid #f1f3f4;
            height: auto;
            min-height: 420px;
        }

        .product-card:last-child {
            border-bottom: none;
        }

        .banner-title {
            font-size: 28px;
            margin: 30px 0 20px 0;
        }
    }

    @media (max-width: 480px) {
        .expertsender-banner {
            grid-template-columns: 1fr;
        }

        .product-image {
            width: 200px;
            height: 200px;
        }
    }
</style>

@{
    var recommendations = Model.GetRecommendations(4);
}

<div class="expertsender-banner">
    <h3 class="banner-title">Featured products</h3>
    @foreach (var product in recommendations.Products)
    {
        <div class="product-card">
            @if (decimal.TryParse(product?.PriceString, out decimal regularPrice) &&
                 decimal.TryParse(product?.CurrentPriceString, out decimal salePrice) &&
                 regularPrice > 0 && salePrice > 0)
            {
                <div class="discount-badge">@Math.Round(((regularPrice - salePrice) / regularPrice) * 100, 0)% OFF</div>
            }

            <img src="@product?.ImageUrl" alt="@product?.Name" class="product-image">
            <div class="product-name">@product?.Name</div>

            <div class="price-section">
                @if (!string.IsNullOrWhiteSpace(product?.CurrentPriceString))
                {
                    <div class="sale-price">@product?.CurrentPriceString @recommendations.Currency.Sign</div>
                    <div class="original-price">@product?.PriceString @recommendations.Currency.Sign</div>
                }
                else
                {
                    <div class="current-price">@product?.PriceString @recommendations.Currency.Sign</div>
                }
            </div>

            <a href="@product?.Url" class="buy-button">Shop now</a>
        </div>
    }
</div>

Banner preview:

25% OFF
Red Hoodie
Red Hoodie
$89.99
$119.99
Shop now
Red T-Shirt
Red T-Shirt
$29.99
Shop now
15% OFF
Gray Hoodie
Gray Hoodie
$76.49
$89.99
Shop now
Gray T-Shirt
Gray T-Shirt
$24.99
Shop now

Product data debug table

This table is useful for checking what data the recommendation engine returns. It displays all available product properties in a structured format, so you can verify the output before building your banner.

@{
    var recommendations = Model.GetRecommendations(4);
}
<style>
    table.custom-table {
        border-collapse: collapse;
        width: 100%;
    }
    table.custom-table, table.custom-table th, table.custom-table td {
        border: 1px solid black;
    }
    table.custom-table th, table.custom-table td {
        padding: 8px;
    }
</style>

<table class="custom-table">
    <thead>
        <tr>
            <th>Product</th>
            <th>Image</th>
            <th>Name</th>
            <th>Link</th>
            <th>ID</th>
            <th>Category</th>
            <th>Regular price</th>
            <th>Sale price</th>
            <th>Omnibus price</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in recommendations.Products
            .Where(p => p != null)
            .Select((p, index) => new { Product = p, Index = index }))
        {
            <tr>
                <td>Product @item.Index</td>
                <td><img src="@item.Product.ImageUrl" alt="Product @item.Index" width="100"/></td>
                <td>@item.Product.Name</td>
                <td><a href="@item.Product.Url">@item.Product.Url</a></td>
                <td>@item.Product.Id</td>
                <td>@item.Product.Category</td>
                <td>@item.Product.PriceString @recommendations.Currency.Sign</td>
                <td>@item.Product.CurrentPriceString @recommendations.Currency.Sign</td>
                <td>@item.Product.OmnibusPriceString @recommendations.Currency.Sign</td>
            </tr>
        }
    </tbody>
</table>

Best practices#

  • Always use null-conditional operators (?.) when accessing product properties to prevent errors.
  • Check for null or empty values before displaying content.
  • Use responsive design principles to ensure your banners work across all devices.
  • Test your banners with different products counts – the method may return fewer products than requested.
  • Consider loading states and empty states in your implementation.
  • Use CurrentPriceString for the sale price and fall back to PriceString if it’s empty.
  • When displaying a discounted price, always show OmnibusPriceString alongside it to comply with the EU Omnibus Directive.