Skip to main content
Each product has one detail page for pricing, checkout behavior, shipping, fulfillment, subscriptions, SEO, and custom data.

Creating a Product

Click Add Product in the Products list to open the quick-create modal. Enter the essential fields (Title, Code, Type, Classification, Price) and click Create product. You are automatically taken to the full Product Detail Page to complete configuration.

Product Detail Page

Navigate to the Product Detail Page by clicking any product code or title in the Products list. The page uses a persistent tab layout. Changes are saved by clicking Save in the top toolbar.

General Tab

Core product identity, titles, images, descriptions, category assignment, and SEO metadata.
FieldDescription
TypePhysical, Digital, or Service
CodeUnique identifier used in buy links (read-only after creation)
ClassificationMain, Upsell, Downsell, Bump, or Bonus
StatusActive, Draft, or Archived
TitlePrimary display name
Checkout TitleOverrides Title at checkout (optional)
Product ImagePrimary product image — upload directly from this tab
GalleryAdditional product images — upload files directly or remove existing images via the visual thumbnail picker
Short DescriptionBrief summary for product cards and search results
Full DescriptionRich text or plain text description
Category / SubcategoryAssign from brand-level product categories
SEO TitlePage title for search engines
SEO SlugURL slug for storefront product pages
SEO DescriptionMeta description (max 160 chars)

Pricing Tab

Selling price, retail (compare-at) price, currency, and tax settings.
FieldDescription
Retail PriceCompare-at price shown as strikethrough
Discounted PriceActual selling price
Currency3-letter currency code (e.g. USD)
Tax ExemptWhen enabled, no tax is calculated at checkout
Tax CategoryTaxJar TIC or Avalara tax code

Subscription Tab

Visible only when This is a subscription product is enabled. See Subscriptions Overview for full billing documentation.
FieldDescription
Is SubscriptionToggle to enable recurring billing
Build-a-BoxCustomers select multiple items for their subscription box
Billing FrequencyHow often to bill (e.g. 1)
Frequency UnitDay / Week / Month / Year
Trial DaysFree trial days before first charge
First Charge FreeCustomer pays shipping only on first order
Subscription TiersMultiple billing frequencies at different prices (toggle to enable)
Subscribe & Save (%)Discount when customer chooses subscription over one-time purchase
Stepped PricingDifferent prices at specific billing cycles (toggle to enable)
Prepaid Options3, 6, 12-month prepaid discounts

Fulfillment Tab

Warehousing, inventory, cost breakdown, SKU categorisation, product specifications, and shipping profile.
SectionFields
Identity & StructureSKU, Barcode, Units per Package, Weight (oz), Dimensions (L/W/H cm)
IntegrationFulfillment Integration override, Multiply SKU toggle
InventoryStock Quantity, Out-of-Stock Policy (continue/deny)
Cost BreakdownProduct Cost, Fulfillment Cost, Packaging Cost, Avg. Shipping, Fuel Surcharge, Auto-calculated COGS
SKU CategorisationSKU Category, SKU Subcategory (for internal reporting)
Product SpecificationsRepeater: Name / Value / Unit pairs (e.g. Dimensions: 10×5×3 cm)
Shipping ProfileAssign a brand-level shipping profile

Merchants Tab

Configure product identifiers per payment gateway integration. For each active payment gateway (NMI, Stripe, BuyGoods, ClickBank, etc.) enter the product ID used on that gateway. These are exposed as product | merchantPriceId:'stripe' in templates.

Bonuses Tab

Assign bonus products that customers receive when purchasing this product. Select from products with classification = bonus.

Files Tab

Visible when product type is Digital or classification is Bonus. Add one or more downloadable files:
  • Purpose — Digital or Bonus
  • Title — File display name
  • Description — Optional description
  • Image — Thumbnail image
  • File — Upload or URL
  • Primary — Mark as primary file for this purpose

Courses Tab

Assign courses that customers are automatically enrolled in after purchase.

Attributes Tab

Fill in values for brand-level Product Attributes defined under Settings → Products → Product Attributes. Supports all field types: text, textarea, rich text, number, boolean, date, select, multiselect, color, image URL, file URL, and JSON. Attributes with applies_to = variant are managed per variant in the Variants tab, not here.

Custom Attributes Tab

Legacy unstructured key/value pairs for ad-hoc product metadata (e.g. a download URL or custom flag).
Note: For structured, typed attributes, use the Attributes tab with Product Attribute definitions instead. Custom Attributes are a legacy system.

Variants Tab

Define product variants (e.g. Color, Size) with individual pricing.
  1. Select variant options — Check which brand-level variant options apply to this product (managed under Settings → Variant Options)
  2. Add variants — Each row shows one field per selected option plus Code and Price

Template Reference

All product data is available in ElasticFunnels templates via data functions and filters. Functions are called with {% set x = FunctionName(args) %} or inline. Filters are applied with {{ value | filterName:arg }}.

Product Object Shape

Every product returned by a data function has the following fields:
FieldTypeDescription
idnumberUnique product ID
brand_idnumberBrand this product belongs to
titlestringDisplay title — uses checkout_title when set, otherwise title
checkout_titlestring | nullRaw checkout title (before effective resolution)
codestringUnique product code used in buy links
type"physical" | "digital" | "service"Product type
classification"main" | "upsell" | "downsell" | "bump" | "bonus"Role in the funnel
status"active" | "draft" | "archived"Visibility status
pricenumberSelling price
retail_pricenumber | nullCompare-at (strikethrough) price
currencystring | null3-letter currency code (e.g. "USD"); null = brand default
skustring | nullStock-keeping unit
barcodestring | nullUPC / EAN barcode
unitsnumber | nullPackage quantity
physical_units_per_productnumber | nullIndividual units inside one package (for per-unit pricing)
descriptionstring | nullFull product description (HTML)
short_descriptionstring | nullBrief summary
imagestring | nullPrimary image CDN URL
gallerystring[]Additional image CDN URLs — always an array, never null
has_gallerybooleantrue when gallery.length > 0
has_descriptionbooleantrue when description is non-empty
in_stockbooleantrue when inventory_quantity is null or > 0
inventory_quantitynumber | nullCurrent stock count; null = unlimited
inventory_policy"continue" | "deny"Behaviour when out of stock
is_subscriptionbooleanWhether recurring billing is enabled
subscription_frequencynumber | nullBilling interval value (e.g. 1)
subscription_frequency_unit"day" | "week" | "month" | "year" | nullBilling interval unit
subscription_trial_daysnumber | nullFree trial days before first charge
subscription_first_charge_freebooleanFirst order ships free (customer pays shipping only)
is_build_a_boxbooleanBuild-a-Box subscription mode
seo_titlestring | nullSEO page title
seo_slugstring | nullURL slug for storefront product pages
seo_descriptionstring | nullMeta description
product_category_idnumber | nullAssigned category ID
product_subcategory_idnumber | nullAssigned subcategory ID
category_namestring | nullCategory display name (denormalised)
subcategory_namestring | nullSubcategory display name (denormalised)
product_filesProductFile[]All attached files
digital_filesProductFile[]Subset: digital download files
bonus_filesProductFile[]Subset: bonus files
primary_download_urlstring | nullURL of the primary digital file
merchant_product_idsRecord<string, string>Gateway → merchant product ID map (e.g. { stripe: "price_xxx" })

ProductFile shape

Each item in product_files, digital_files, and bonus_files:
FieldTypeDescription
urlstringCDN URL of the file
titlestring | nullFile display name
descriptionstring | nullFile description
imagestring | nullThumbnail image URL
purpose"digital" | "bonus"Whether this is a digital or bonus file
kind"audio" | "video" | "document" | "image" | "archive" | "file"Inferred from MIME type
mime_typestring | nullMIME type
sizenumber | nullFile size in bytes
extensionstring | nullFile extension (e.g. "pdf")
is_primarybooleanWhether this is the primary file for its purpose
sort_ordernumberDisplay order

Data Functions

GetProduct(code)

Loads a single product by its code for the current brand. Also pre-loads structured attribute values so the attr and attrLabel filters work immediately on the returned object.
{% set product = GetProduct('PROD-001') %}
{{ product.title }} — {{ product.price | formatPrice:product.currency }}
Returns: ProductObject | null

GetProducts(filters?)

Returns all active products for the brand, with optional filters.
Filter keyTypeDescription
typestring"physical", "digital", or "service"
classificationstring"main", "upsell", "downsell", "bump", or "bonus"
codesstringComma-separated list of product codes
category_idnumber | stringFilter by product category ID
subcategory_idnumber | stringFilter by subcategory ID
{% set upsells = GetProducts({ classification: 'upsell' }) %}
{% for p in upsells %}
  <div>{{ p.title }} — {{ p.price | formatPrice:p.currency }}</div>
{% endfor %}
Returns: ProductObject[]

GetAllProducts()

Returns every product for the brand without filters. Fastest when you need to iterate all products and filter in the template.
{% set all = GetAllProducts() %}
{% for p in all %}{% if p.status == 'active' %}
  {{ p.title }}
{% endif %}{% endfor %}
Returns: ProductObject[]

GetProductCategories(filters?)

Returns product categories. By default returns a full tree ordered by position, then name, with a depth field.
Filter keyTypeDescription
top_levelbooleanOnly root categories (no parent)
parent_idnumber | stringOnly direct children of this category
flatbooleanReturn flat list instead of tree
treebooleanSet false to force flat list
Each item: { id, name, position, parent_id, depth? }
{% set cats = GetProductCategories({ top_level: true }) %}
{% for cat in cats %}
  <h3>{{ cat.name }}</h3>
  {% set subs = GetProductCategories({ parent_id: cat.id }) %}
  {% for sub in subs %}<span>{{ sub.name }}</span>{% endfor %}
{% endfor %}
Returns: CategoryObject[]

ProductDownloads(product)

Flattens all downloadable files attached to a product into a single deduplicated array. Useful for rendering download lists on digital product pages.
{% set files = ProductDownloads(product) %}
{% for f in files %}
  <a href="{{ f.url }}">{{ f.title or f.extension | upper }} — {{ f.kind }}</a>
{% endfor %}
Each item: { url, title, kind, purpose, extension, mime_type, size, is_primary, sort_order } Returns: DownloadObject[]

ProductSavings(product)

Returns a structured savings breakdown comparing retail_price to price. Returns null if there is no saving.
{% set savings = ProductSavings(product) %}
{% if savings and savings.amount > 0 %}
  You save {{ savings.amountFormatted }} ({{ savings.percent }}% off)
{% endif %}
Returns: { amount: number, percent: number, amountFormatted: string, percentFormatted: string } | null

SubscriptionSummary(product)

Generates a human-readable billing summary string for a subscription product (e.g. "7-day free trial · Every 1 month").
{% if product.is_subscription %}
  <p>{{ SubscriptionSummary(product) }}</p>
{% endif %}
Returns: string

GetProductAttributes(filters?)

Returns the brand’s structured attribute definitions. See Product Attributes for details on attribute types and scoping.
Filter keyTypeDescription
visiblebooleanOnly attributes with visible_on_product_page = true
filterablebooleanOnly attributes marked as filterable
applies_to"product" | "variant"Filter by scope
Each item: { id, name, slug, type, applies_to, options, filterable, visible_on_product_page, required } Returns: AttributeDefinition[]

GetProductAttributeValues(product)

Returns all attribute values for a product as a { slug: value } map.
{% set defs = GetProductAttributes({ visible: true }) %}
{% set vals = GetProductAttributeValues(product) %}
{% for attr in defs %}
  <dt>{{ attr.name }}</dt>
  <dd>{{ vals[attr.slug] }}</dd>
{% endfor %}
Returns: Record<string, string | number | boolean>

Filters

formatPrice

Formats a numeric price using the brand’s active currency locale.
{{ product.price | formatPrice:product.currency }}
{# → "$49.00" #}

{{ 9.99 | formatPrice:'EUR' }}
{# → "€9.99" #}

savings

Computes the saving between retail_price and price and returns a formatted string.
FormatExample output
(default)"Save $20"
'percent'"Save 25%"
'both'"Save $20 (25% off)"
{{ product | savings }}
{{ product | savings:'percent' }}
{{ product | savings:'both' }}
Returns "" when retail_price is not set or not greater than price.

compareAt

Formats retail_price as a “Was $X” string.
{{ product | compareAt }}
{# → "Was $99.00" #}
Returns "" when retail_price is not set.

pricePerUnit

Divides price by physical_units_per_product to give the per-unit price. Useful for “per bottle” or “per capsule” displays.
{{ product | pricePerUnit }}
{# → "33.00" (for a 6-pack at $198) #}
Returns: string (float formatted to 2 decimal places)

subscriptionLabel

Short-form billing label. Returns trial info if present, otherwise the billing frequency.
{{ product | subscriptionLabel }}
{# → "7-day free trial"  or  "Every 1 month" #}
Returns "" for non-subscription products.

merchantPriceId

Looks up the merchant-specific product identifier from merchant_product_ids (configured in the Merchants tab).
{{ product | merchantPriceId:'stripe' }}
{# → "price_1abc..." #}

{{ product | merchantPriceId:'nmi' }}

attr

Returns the value of a structured attribute by its slug. Only works on products loaded via GetProduct (which pre-loads attribute values).
{% set product = GetProduct('MY-PRODUCT') %}
{{ product | attr:'material' }}
{# → "Organic Cotton" #}
Returns "" when the attribute is not set.

attrLabel

Returns the human-readable label (name) of a structured attribute definition by slug.
{{ product | attrLabel:'material' }}
{# → "Material" #}

product.gallery is a string[] — an array of CDN image URLs. It is always an array (never null or a JSON string). product.has_gallery is true when the array has at least one entry.
{% if product.has_gallery %}
<div class="product-gallery">
  {% for url in product.gallery %}
  <img src="{{ url }}" alt="{{ product.title }}" />
  {% endfor %}
</div>
{% endif %}
Show the primary image with gallery fallback:
{% set mainImage = product.image or (product.gallery[0] if product.has_gallery) %}
{% if mainImage %}
<img src="{{ mainImage }}" alt="{{ product.title }}" />
{% endif %}