Skip to content

Miva Template Language (aka MVT, SMT)

Key Points

Variables

Use Snake Case

Use Snake Case for Miva variables and input[name] values.

Incorrect
<mvt:assign name="l.thisIsHardToRead" value="''">
<mvt:assign name="l.settings:product:variantInformation:variants" value="''">
<input type="text" name="fooBar" value="&mvte:global:fooBar;">
Correct
<mvt:assign name="l.this_is_easy_to_read" value="''">
<mvt:assign name="l.settings:product:variant_information:variants" value="''">
<input type="text" name="foo_bar" value="&mvte:global:foo_bar;">

Use Lowest Scope Possible

  • First use l.
    • If you do not need to output the variable, or use it with an <mvt:item />, lean towards utilizing just local.
    • The l. scope is limited to the specific template section it is defined within. For example, if you define l.my_var in the main template of a page, and then try to reference it within the "Content" template of a page, it will be undefined.
    • &mvt Output: None
  • Then use l.settings
    • Use l.settings (or l.all_settings when applicable) if you need to do any of the following:
      • Output the variable using &mvt(a|e|j)
      • Use it within an <mvt:item />
      • Make it available across several different templates
  • Otherwise use g.
Incorrect
<mvt:assign name="g.show_greeting" value="g.Action EQ 'LOGN'" />
<mvt:assign name="g.full_name" value="g.Customer:bill_fname $ ' ' $ g.Customer:bill_lname" />

<mvt:if expr="g.show_greeting">
    Hello &mvte:global:first_name;!
</mvt:if>
Correct
<mvt:assign name="l.show_greeting" value="g.Action EQ 'LOGN'" />
<mvt:assign name="l.settings:full_name" value="g.Customer:bill_fname $ ' ' $ g.Customer:bill_lname" />

<mvt:if expr="l.show_greeting">
    Hello &mvte:full_name;!
</mvt:if>

Reserve Global Scope For Accessing Data From POST/GET HTTP Variables

In Miva, global variables should be used for accepting GET/POST variables and then validated & sanitized as they get assigned to lower scoped variables. Also, provide fallbacks/defaults when the global variables are not provided.

Incorrect
<mvt:assign name="g.product_code" value="l.settings:product:code" />
<mvt:assign name="g.quantity" value="l.settings:product:quantity" />
Correct
<mvt:assign name="l.product_code" value="trim(g.Product_Code)" />
<mvt:assign name="l.quantity" value="int(trim(g.Quantity))" />
Incorrect
<mvt:assign name="l.customer_first_name" value="'John'" />
<mvt:assign name="l.customer_address" value="'16870 W Bernardo Dr Ste 100, San Diego, CA 92127'" />
<mvt:assign name="l.customer_email" value="'mivamerchant@miva.com'" />
Correct
<mvt:assign name="l.customer:first_name" value="'John'" />
<mvt:assign name="l.customer:address" value="'16870 W Bernardo Dr Ste 100, San Diego, CA 92127'" />
<mvt:assign name="l.customer:email" value="'mivamerchant@miva.com'" />

Use Descriptive Variable Names

See Comments > Best Practices and Clean Code > Names Rules for all best-practices.

Name your variables in relation to what they are. Don't use things like l.test or l.val. Instead, specify what they are.

Incorrect
<mvt:assign name="l.my_count" value="0" />
<mvt:assign name="l.my_array" value="''" />
Correct
<mvt:assign name="l.also_bought:count" value="0" />
<mvt:assign name="l.also_bought:items" value="''" />
Incorrect
<mvt:assign name="l.settings:product:my_variable" value="'Acme Inc. Brand'" />
Correct
<mvt:assign name="l.settings:product:brand" value="'Acme Inc. Brand'" />

Set Default Values Of Variables

If you're setting variables within a condition and/or using global variables that may/may-not be set, setting default values can save you from some un-intended consequences.

Incorrect

<mvt:if expr="'@miva.com' IN g.Customer:bill_email">
    <mvt:assign name="g.Allow_Checkout" value="1" />
</mvt:if>

<mvt:if expr="g.Allow_Checkout">
    <a href="&mvte:urls:OCST:auto;">Checkout</a>
</mvt:if>
Someone could just pass ?Allow_Checkout=1 in the URL and gain access.

Correct

<mvt:assign name="g.Allow_Checkout" value="0" />
<mvt:if expr="'@miva.com' IN g.Customer:bill_email">
    <mvt:assign name="g.Allow_Checkout" value="1" />
</mvt:if>

<mvt:if expr="g.Allow_Checkout">
    <a href="&mvte:urls:OCST:auto;">Checkout</a>
</mvt:if>
Someone would truly have an g.Customer:bill_email with @miva.com in it.

Avoid the NULL Keyword

Do not use NULL in variable assignments or checks. Use an empty string (ex: '') instead. NULL evaluates to g.NULL which could be populated with data from a POST or GET HTTP method which could result in unexpected functionality (at best) or XSS (at worst).

Incorrect
<mvt:assign name="l.product_code" value="NULL" />
<mvt:assign name="l.category_code" value="null" />
Correct
<mvt:assign name="l.product_code" value="''" />
<mvt:assign name="l.category_code" value="''" />

Use Pascal Case For Key Global Variables

References to the following Global Miva Variables should be in Pascal Case. This promotes consistency with how the LSK treats these variables:

  • g.Basket
  • g.Store
  • g.Customer
  • g.Session
  • g.Domain
  • g.User
Incorrect
<mvt:if expr="g.customer:id">
    Hello &mvte:global:customer:bill_fname;
</mvt:if>
Correct
<mvt:if expr="g.Customer:id">
    Hello &mvte:global:Customer:bill_fname;
</mvt:if>

Spacing

See Files for general indentation & spacing rules.

Use Proper Indentation

Indentation should follow the linter (most likely tabs), and the indentation should be properly formatted.

Incorrect

<mvt:foreach iterator="category" array="categories">
<mvt:foreach iterator="product" array="category:products">
<mvt:foreach iterator="part" array="product:parts">
&mvte:category:name; - &mvte:product:name; - &mvte:part:name;
            </mvt:foreach></mvt:foreach></mvt:foreach>
Reasoning: The indentation is not utilizing the linter rules (using spaces, not tabs), and it's not properly formatted..

Correct

<mvt:foreach iterator="category" array="categories">
    <mvt:foreach iterator="product" array="category:products">
        <mvt:foreach iterator="part" array="product:parts">
            &mvte:category:name; - &mvte:product:name; - &mvte:part:name;
        </mvt:foreach>
    </mvt:foreach>
</mvt:foreach>
Reasoning: Indentation is using tabs, and properly formatted*

Two Newlines Before Opening & Closing Tags

One extra line space should always both precede AND follow any tag that has an opening and closing that are NOT placed on the same line.

Incorrect
<mvt:assign name="l.offset" value="int( g.Offset )" />
<mvt:assign name="l.hide_hdft"  value="0" />
<mvt:if expr="l.offset GT 0">
    <mvt:assign name="l.hide_hdft" value="1" />
</mvt:if>
Correct
<mvt:assign name="l.offset" value="int( g.Offset )" />
<mvt:assign name="l.hide_hdft"  value="0" />

<mvt:if expr="l.offset GT 0">
    <mvt:assign name="l.hide_hdft" value="1" />
</mvt:if>

The only exception to this rule is if there is a single (un-grouped) assignment and the following tag immediately uses that assignment.

Incorrect
<mvt:assign name="l.offset" value="int( g.Offset )" />

<mvt:if expr="l.offset GT 0">
    <mvt:assign name="l.hide_hdft" value="1" />
</mvt:if>
Correct
<mvt:assign name="l.offset" value="int( g.Offset )" />
<mvt:if expr="l.offset GT 0">
    <mvt:assign name="l.hide_hdft" value="1" />
</mvt:if>

Space Before Self-Closing Tag

One space should follow the quotations of a mvt tag when being closed.

Incorrect
<mvt:assign name="l.example_string" value="'My Value'"/>
Correct
<mvt:assign name="l.example_string" value="'My Value'" />

One Space Between Attributes

Spacing between the mvt tags attributes should only be 1 space, unless aligning with multiple lines.

Incorrect
<mvt:assign name="l.example_value"  value="'My Value'" />
<mvt:do file="g.Module_Library_DB"   name="l.product_loaded"    value="Product_Load_Code_Cached( l.product_code, l.product )" />
Correct
<mvt:assign name="l.example_value" value="'My Value'" />
<mvt:do file="g.Module_Library_DB" name="l.product_loaded" value="Product_Load_Code_Cached( l.product_code, l.product )" />
Incorrect
<mvt:assign name="l.groups"  value="''" />
<mvt:assign name="l.groups:one" value="'My Value'" />
<mvt:assign name="l.groups:two"     value="'My Other Value'" />
Correct
<mvt:assign name="l.groups"     value="''" />
<mvt:assign name="l.groups:one" value="'My Value'" />
<mvt:assign name="l.groups:two" value="'My Other Value'" />
Correct
<mvt:assign name="l.groups" value="''" />
<mvt:assign name="l.groups:one" value="'My Value'" />
<mvt:assign name="l.groups:two" value="'My Other Value'" />

No Spaces Around Equal Signs

There should not be any spaces between the mvt-tag's attribute, equal sign, and quotations.

Incorrect
<mvt:assign name = "l.hello" value = "'World'" />
Correct
<mvt:assign name="l.hello" value="'World'" />

Multi-line Large Concatenated Strings

When concatenating strings use new lines to make the code easier to visualize.

Incorrect
<mvt:assign name="l.product_code" value="'1234'" />
<mvt:assign name="l.category_code" value="'shirts'" />
<mvt:assign name="l.screen" value="'PROD'" />

<mvt:assign name="l.url_to_redirect" value="'https://www.yourdomain.com?Product_Code=' $ l.product_code $ '&Category_Code=' $ l.category_code $ '&Screen=' $ l.screen" />
Correct
<mvt:assign name="l.product_code" value="'1234'" />
<mvt:assign name="l.category_code" value="'shirts'" />
<mvt:assign name="l.screen" value="'PROD'" />

<mvt:assign name="l.url_to_redirect" value="
    'https://www.yourdomain.com?' $
    'Product_Code=' $ l.product_code $
    '&Category_Code=' $ l.category_code $
    '&Screen=' $ l.screen
" />

Conditionals

Avoid Maintaining Duplicate HTML Within Conditionals

This commonly occurs with HTML inputs that are checked, selected, or disabled. Instead, use variables, mvt:captures, or mvt:do's.

Checked/Required:

Incorrect
<mvt:foreach iterator="option" array="attribute:options">
    <label>
        <mvt:if expr="l.settings:option:required AND l.settings:option:checked">
            <input type="radio" name="Product_Attributes[&mvte:attribute:index;]:value" value="&mvte:option:code;" required checked>
        <mvt:elseif expr="l.settings:option:required">
            <input type="radio" name="Product_Attributes[&mvte:attribute:index;]:value" value="&mvte:option:code;" required>
        <mvt:else>
            <input type="radio" name="Product_Attributes[&mvte:attribute:index;]:value" value="&mvte:option:code;">
        </mvt:if>
        <span class="x-product-option__caption">
            &mvte:option:prompt;
        </span>
    </label>
</mvt:foreach>
Correct
<mvt:foreach iterator="option" array="attribute:options">
    <mvt:assign name="l.settings:option:html" value="''" />

    <mvt:if expr="l.settings:option:required">
        <mvt:assign name="l.settings:option:html:required" value="'required'">
    </mvt:if>

    <mvt:if expr="l.settings:option:checked">
        <mvt:assign name="l.settings:option:html:checked" value="'checked'">
    </mvt:if>

    <label>
        <input type="radio" name="Product_Attributes[&mvte:attribute:index;]:value" value="&mvte:option:code;" &mvte:option:html:required; &mvte:option:html:checked;>
        <span class="x-product-option__caption">
            &mvte:option:prompt;
        </span>
    </label>
</mvt:foreach>

Selecting Options:

Use DrawOption() for to select <option> elements based on variables.

<mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( value, default, text )" />`
Incorrect
<label class="c-form-label" for="l-sort_by">Sort</label>
<div class="c-form-select">
    <select id="l-sort_by" class="c-form-select__dropdown-display" name="Sort_By" onchange="MMProdList_UpdateQuery( this ); return true;">
        <mvt:if expr="g.Sort_By EQ 'disp_order'">
            <option value="disp_order" selected>Featured</option>
        <mvt:else>
            <option value="disp_order">Featured</option>
        </mvt:if>
        <mvt:if expr="g.Sort_By EQ 'bestsellers'">
            <option value="bestsellers" selected>Best</option>
        <mvt:else>
            <option value="bestsellers">Best</option>
        </mvt:if>
        <mvt:if expr="g.Sort_By EQ 'price_asc'">
            <option value="price_asc" selected>Price</option>
        <mvt:else>
            <option value="price_asc">Price</option>
        </mvt:if>
        <mvt:if expr="g.Sort_By EQ 'price_desc'">
            <option value="price_desc" selected>Price</option>
        <mvt:else>
            <option value="price_desc">Price</option>
        </mvt:if>
        <mvt:if expr="g.Sort_By EQ 'newest'">
            <option value="newest" selected>Newest</option>
        <mvt:else>
            <option value="newest">Newest</option>
        </mvt:if>
    </select>
</div>
Correct
<label class="c-form-label" for="l-sort_by">Sort</label>
<div class="c-form-select">
    <select id="l-sort_by" class="c-form-select__dropdown-display" name="Sort_By" onchange="MMProdList_UpdateQuery( this ); return true;">
        <mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'disp_order', g.Sort_By, 'Featured' )" />
        <mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'bestsellers', g.Sort_By, 'Best Selling' )" />
        <mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'price_asc', g.Sort_By, 'Price (Low to High)' )" />
        <mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'price_desc', g.Sort_By, 'Price (High to Low)' )" />
        <mvt:do file="g.Module_Library_Utilities" name="l.success" value="DrawOption( 'newest', g.Sort_By, 'Newest Items' )" />
    </select>
</div>

Pluralizing Words:

Incorrect
<mvt:if expr="l.settings:basket:item_count EQ 1">
    <span class="t-checkout__order-summary-content-label">Your basket has &mvte:basket:item_count; Item</span>
<mvt:else>
    <span class="t-checkout__order-summary-content-label">Your basket has &mvte:basket:item_count; Items</span>
</mvt:if>
Correct
<mvt:do file="g.Module_Library_Utilities" name="l.settings:basket:item_count_label" value="Plural( l.settings:basket:item_count, 'Item', 'Items' )" />
<span class="t-checkout__order-summary-content-label">Your basket has &mvte:basket:item_count; &mvte:basket:item_count_label;</span>

Comments

mvt:dos

  • Always confirm that your parameters are utilizing the right type.
    • If the function you're calling requires a variable, do not set a string. This will cause an error.
    • Always make sure that you have the correct filename, if not miva will error and add to the error log. This log can get large if this is a function running on almost every page load, and it will cause your line of code to not work properly.
  • Always make sure to send the necessary data needed for your function call. If the function is expecting a specific variable set-up, make sure you have the necessary info! We don't want an unwanted errors.

Clear DB Insert & Update Structures

When utilizing inserts/updates (especially in a loop) clear out the main variable.

When miva does inserts/updates, it will return the variable you sent, back, with the updated information. By not clearing the variable out, you may run into unwanted duplicates.

Incorrect

<mvt:foreach iterator="extra_product" array="extra_products">
    <mvt:assign name="l.basketitem:basket_id"   value="g.Basket:id" />
    <mvt:assign name="l.basketitem:product_id"  value="l.settings:extra_product:id" />

    <mvt:do file="g.Module_Library_DB" name="l.success" value="BasketItem_Insert( l.basketitem )" />
</mvt:foreach>
What happens here is that miva will return the line_id and group_id onto the l.basketitem so then next iteration will have that too

Correct
<mvt:foreach iterator="extra_product" array="extra_products">
    <mvt:assign name="l.basketitem"             value="''" />
    <mvt:assign name="l.basketitem:basket_id"   value="g.Basket:id" />
    <mvt:assign name="l.basketitem:product_id"  value="l.settings:extra_product:id" />

    <mvt:do file="g.Module_Library_DB" name="l.success" value="BasketItem_Insert( l.basketitem )" />
</mvt:foreach>

Use Meaningful mvt:do[name] Variables

Utilize your mvt:do[name] variables wisely. If a function returns a value (success/fail, 1/0, count of the array, etc.) utilize that if necessary.

If you have an mvt:do that returns the count of the array, do not utilize miva_array_elements() later.

Incorrect
<mvt:do file="g.Module_Library_DB" name="l.result" value="Product_Load_Code( 'test', l.product)" />
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code" value="Product_Load_Code( 'test', l.product)" />
Correct
<mvt:do file="g.Module_Library_DB" name="l.test_product_loaded" value="Product_Load_Code( 'test', l.product)" />
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code_Result" value="Product_Load_Code( 'test', l.product)" />

Use The Return Value From The Function Instead Of The Argument

Incorrect
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code" value="Product_Load_Code( 'test', l.product )" />

<mvt:if expr="NOT ISNULL l.product">
    <!-- Do something when the product exists -->
</mvt:if>
Correct
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code_Result" value="Product_Load_Code( 'test', l.product )" />

<mvt:if expr="l.Product_Load_Code_Result">
    <!-- Do something when the product exists -->
</mvt:if>

_Inserts() & _Update() Structures Should Match Software's Structure

We recommend downloading the latest Limited Source Kit for Miva https://apps.miva.com/miva-merchant-limited-source-kit.html. Which has the source code for all public functions. This allows you to review the structure and the structure's members and to determine what the function expects.

Incorrect
<mvt:assign name="l.product_page_uri:page_id" value="1" />
<mvt:assign name="l.product_page_uri:cat_id" value="2" />
<mvt:assign name="l.product_page_uri:product_id" value="3" />
<mvt:assign name="l.product_page_uri:feed_id" value="0" />

<mvt:do file="g.Module_Feature_URI_DB" name="l.success" value="URI_Insert( l.product_page_uri )" />
Correct
<mvt:comment>
|
|   Product Page URI Insert
|
</mvt:comment>
<mvt:assign name="l.uri:page_id" value="1" />
<mvt:assign name="l.uri:cat_id" value="2" />
<mvt:assign name="l.uri:product_id" value="3" />
<mvt:assign name="l.uri:feed_id" value="0" />

<mvt:do file="g.Module_Feature_URI_DB" name="l.success" value="URI_Insert( l.uri )" />

null

MivaScript doesn't have a NULL reserved keyword so it's important to only use it in the correct context or find another alternative.

NEVER assign or use NULL or g.NULL.

In the following examples, NULL applies to all cases of null: g.NULL, NULL, g.null, null, g.Null, Null, etc.

Don't Use NULL in Comparisons:

Incorrect
<mvt:if expr="g.Product_Code EQ NULL">
    <!-- Do something -->
</mvt:if>
Correct
<mvt:if expr="ISNULL g.Product_Code">
    <!-- Do something -->
</mvt:if>

Don't Assign Variables to NULL:

Incorrect
<mvt:do file="g.Module_Library_DB" name="null" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.found_error" value="NULL" />
Correct
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code_Result" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.found_error" value="''" />

l.null Is Ok For Unused Return Values

l.null is ok when you do not need to use the return variable for anything (i.e. running an mvt:do and not needing the return value).

Incorrect
<mvt:do file="g.Module_Library_DB" name="null" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="NULL" value="miva_array_insert(l.foo, 'Foobar', -1)" />
Correct
<mvt:do file="g.Module_Library_DB" name="l.null" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.null" value="miva_array_insert(l.foo, 'Foobar', -1)" />
Correct (Best)
<mvt:do file="g.Module_Library_DB" name="l.Product_Load_Code_Result" value="Product_Load_Code( 'test', l.product )" />
<mvt:assign name="l.index" value="miva_array_insert(l.foo, 'Foobar', -1)" />

Don't Pass NULL into a Function

Don't pass NULL into a function. Try to initialize and utilize l.null to an empty-string.

Incorrect
<mvt:do file="g.Module_Feature_URI_UT" name="l.basketitem:link" value="Store_Product_URL( l.basketitem:product, NULL )" />
Correct
<mvt:assign name="l.null" value="''" />
<mvt:do file="g.Module_Feature_URI_UT" name="l.basketitem:link" value="Store_Product_URL( l.basketitem:product, l.null )" />

Arrays/Iterators

Iterator Comes Before Array

The iterator attribute should always come before the array attribute in an <mvt:foreach>.

Incorrect
<mvt:foreach array="basket:groups" iterator="group">
    <!-- Do something -->
</mvt:foreach>
Correct
<mvt:foreach iterator="group" array="basket:groups">
    <!-- Do something -->
</mvt:foreach>

Prefer l.posN for Array Indexes

Within an mvt:foreach loop you should reference the current index using l.posN; rather than POSN. This is for a couple main reasons:

  1. It's a best practice to reference variables with their variable scope and to use the lowest scope possible
    • When MivaScript attempts to evaluate a variable without a scope (ex POS1), it first looks to l.pos1 to see if a value is found there, then if it doesn't find a value then it looks to g.pos1.
    • Using l.posN avoids a potential case where you end up referencing a global-variable (query-string/form-data POST) instead of one from mvt:foreach. For example, referencing POS2 when you're not in two nested foreach loops would be like referencing g.POS2)
  2. Variable references in MVT are case-insensitive (l.foo EQ l.FOO ), but using the lower-case better fits with our naming conventions is this is not a constant.
  3. This is how Software writes their pos-counters in the LSK
Incorrect
<mvt:foreach iterator="group" array="basket:groups">
    <mvt:assign name="l.is_even" value="(POS1 MOD 2) EQ 0" />
    <mvt:if expr="l.is_even">
        <mvt:assign name="l.result" value="miva_array_insert( l.evens, l.settings:group, -1 )" />
    </mvt:if>
</mvt:foreach>
Correct
<mvt:foreach iterator="group" array="basket:groups">
    <mvt:assign name="l.is_even" value="(l.pos1 MOD 2) EQ 0" />
    <mvt:if expr="l.is_even">
        <mvt:assign name="l.result" value="miva_array_insert( l.evens, l.settings:group, -1 )" />
    </mvt:if>
</mvt:foreach>

Avoid Unnecessary Iterations

If you are trying to find a single instance of something, it is more beneficial to use miva_array_search or at least a foreachstop.

Don't continue looping through if your code is not going to do anything. You never know how many iterations you'll be looping through. If you can stop the loop as soon as possible, then it will be quicker!

Incorrect
<mvt:assign name="l.has_forbidden_items" value="0" />

<mvt:foreach iterator="group" array="basket:groups">
    <mvt:if expr="l.settings:group:customfield_values:customfields:forbidden">
        <mvt:assign name="l.has_forbidden_items" value="1" />
    </mvt:if>
</mvt:foreach>

<mvt:if expr="l.has_forbidden_items">
    <!-- Do something with permitted items -->
</mvt:if>
Correct
<mvt:assign name="l.has_forbidden_items" value="0" />

<mvt:foreach iterator="group" array="basket:groups">
    <mvt:if expr="l.settings:group:customfield_values:customfields:forbidden">
        <mvt:assign name="l.has_forbidden_items" value="1" />
        <mvt:foreachstop />
    </mvt:if>
</mvt:foreach>

<mvt:if expr="l.has_forbidden_items">
    <!-- Do something with permitted items -->
</mvt:if>
Correct (Best)
<mvt:assign name="l.forbidden_item_index" value="miva_array_search( l.settings:basket:groups, 1, l.group, 'NOT ISNULL l.settings:group:customfield_values:customfields:forbidden' )" />

<mvt:if expr="l.forbidden_item_index EQ 0">
    <!-- Do something with permitted items -->
</mvt:if>

Prefer miva_array_filter

When iterating through an array, and you only want to display certain iterators that match an expression, determine if you should use miva_array_filter, or check the opposite expression and utilize mvt:foreachcontinue.

It would be more useful to use miva_array_filter when you need to make sure there is data to output (miva_array_filter returns the new count of your output var).

Incorrect

<h2>Invalid Addresses</h2>
<div>
    <mvt:foreach iterator="address" array="addressfields:validated_addresses">
        <mvt:if expr="NOT l.settings:address:validated">
            <!-- Do something -->
        </mvt:if>
    </mvt:foreach>
</div>

<h2>Valid Addresses</h2>
<div>
    <mvt:foreach iterator="address" array="addressfields:validated_addresses">
        <mvt:if expr="l.settings:address:validated">
            <!-- Do something -->
        </mvt:if>
    </mvt:foreach>
</div>
Above: Could output a heading & empty div with no content.

Correct

<mvt:assign name="l.settings:addressfields:non_validated_addresses_count"   value="miva_array_filter( l.settings:addressfields:validated_addresses, 1, l.address, 'l.address:validated NE 1', l.settings:addressfields:non_validated_addresses )" />
<mvt:assign name="l.settings:addressfields:validated_addresses_count"       value="miva_array_filter( l.settings:addressfields:validated_addresses, 1, l.address, 'l.address:validated EQ 1', l.settings:addressfields:validated_addresses )" />

<mvt:if expr="l.settings:addressfields:non_validated_addresses_count">
    <h2>Invalid Addresses</h2>
    <div>
        <mvt:foreach iterator="address" array="addressfields:non_validated_addresses">
            <!-- Do something -->
        </mvt:foreach>
    </div>
</mvt:if>

<mvt:if expr="l.settings:addressfields:validated_addresses_count">
    <h2>Valid Addresses</h2>
    <div>
        <mvt:foreach iterator="address" array="addressfields:validated_addresses">
            <!-- Do something -->
        </mvt:foreach>
    </div>
</mvt:if>
Above: Split out validated and non-validated addresses into their own arrays to display different sections.

Use foreachcontinue Early or miva_array_filter Results

Don't have an mvt:if statement around all/most of the mvt:foreach output.

It makes it a little bit more difficult to read and unnecessarily increases the indentation; especially when there is a lot of content & conditionals within the mvt:foreach.

Incorrect
<mvt:foreach iterator="address" array="addressfields:validated_addresses">
    <mvt:if expr="l.settings:address:validated">
        <!-- Do something -->
    </mvt:if>
</mvt:foreach>
Correct
<mvt:foreach iterator="address" array="addressfields:validated_addresses">
    <mvt:if expr="NOT l.settings:address:validated">
        <mvt:foreachcontinue />
    </mvt:if>
    <!-- Do something -->
</mvt:foreach>
Correct
<mvt:assign name="l.settings:addressfields:validated_addresses_count" value="miva_array_filter( l.settings:addressfields:validated_addresses, 1, l.address, 'l.address:validated EQ 1', l.settings:addressfields:validated_addresses )" />

<mvt:foreach iterator="address" array="addressfields:validated_addresses">
    <!-- Do something -->
</mvt:foreach>

Do Not Have Empty if Statement Blocks

Do not have empty mvt:if statement blocks. For example, do not use an mvt:if statement, and only utilize the mvt:else block.

Incorrect
<mvt:foreach iterator="group" array="basket:groups">
    <mvt:if expr="NOT l.settings:group:customfield_values:customfields:forbidden">
        <mvt:comment><!-- Do nothing for permitted groups --></mvt:comment>
    <mvt:else>
        <mvt:assign name="l.forbidden_items_count" value="miva_array_insert_var( l.forbidden_items, l.settings:group, -1 )" />
    </mvt:if>
</mvt:foreach>
<mvt:assign name="l.forbidden_items_count" value="miva_array_elements(l.forbidden_items)" />
Correct
<mvt:assign name="l.forbidden_items_count"  value="0" />
<mvt:assign name="l.forbidden_items"        value="''" />

<mvt:foreach iterator="group" array="basket:groups">
    <mvt:if expr="NOT l.settings:group:customfield_values:customfields:forbidden">
        <mvt:foreachcontinue />
    </mvt:if>

    <mvt:assign name="l.forbidden_items_count" value="miva_array_insert_var( l.forbidden_items, l.settings:group, -1 )" />
</mvt:foreach>
Correct (Best)
<mvt:assign name="l.forbidden_items_count" value="miva_array_filter( l.settings:basket:groups, 1, l.group, 'l.group:customfield_values:customfields:forbidden EQ 1', l.forbidden_items )">

Avoid Referencing Array Indexes

Avoid referencing variables with the square bracket notation; especially if you haven't checked the length of the array.

Incorrect
<mvt:assign name="l.first_product_code" value="l.settings:basket:items[1]:code" />
Correct
<mvt:assign name="l.first_product_code" value="''" />
<mvt:if expr="miva_array_elements(l.settings:basket:items)">
    <mvt:assign name="l.first_product_code" value="l.settings:basket:items[1]:code" />
</mvt:if>

Avoid Square Bracket Notation for Array Creation

When arrays are created through square bracket notation it typically requires more lines of code, is repetitive, and can potentially create runtime errors if the indexes are invalid.

Using functions like miva_array_insert & miva_splitstring can help you avoid some of the issues with square-bracket notation.

This rule is best used when you are building an array while looping through another array:

Incorrect

<mvt:assign name="l.expensive_item_count" value="1" />
<mvt:foreach iterator="group" array="basket:groups">
    <mvt:if expr="l.settings:group:price GT 100">
        <mvt:assign name="l.expensive_items[l.expensive_item_count]" value="l.settings:group" />
        <mvt:assign name="l.expensive_item_count" value="l.expensive_item_count + 1" />
    </mvt:if>
</mvt:foreach>
Note: Miva will throw a fatal error for this.

Correct
<mvt:foreach iterator="group" array="basket:groups">
    <mvt:if expr="l.settings:group:price GT 100">
        <mvt:assign name="l.expensive_item_count" value="miva_array_insert( l.expensive_items, l.settings:group, -1 )" />
    </mvt:if>
</mvt:foreach>

... or when you're looking to convert a static list into an array:

Incorrect
<mvt:assign name="l.fruits[1]" value="'Apple'" />
<mvt:assign name="l.fruits[2]" value="'Banana'" />
<mvt:assign name="l.fruits[2]" value="'Orange'" />
Correct
<mvt:assign name="l.fruits_count" value="miva_array_insert( l.fruits, 'Apple', -1 )" />
<mvt:assign name="l.fruits_count" value="miva_array_insert( l.fruits, 'Banana', -1 )" />
<mvt:assign name="l.fruits_count" value="miva_array_insert( l.fruits, 'Orange', -1 )" />
Correct (Best)
<mvt:assign name="l.fruits_count" value="miva_splitstring( 'Apple, Banana, Orange', ',', l.fruits, 'trim' )" />

JSON

Use JSON_Output For Outputting Complex Structures

Incorrect
<mvt:if expr="l.api_error EQ 1">
{
    "error": 1,
    "message": "Something went wrong!",
    "product": {
        "id": &mvtj:product:id;,
        "code": "&mvtj:product:code;",
        "name": "&mvtj:product:name;"
    }
}
</mvt:if>
Correct
<mvt:if expr="l.api_error EQ 1">
    <mvt:assign name="l.json:error" value="1" />
    <mvt:assign name="l.json:message" value="'Something went wrong!'" />
    <mvt:assign name="l.json:product" value="l.settings:product" />
</mvt:if>
<mvt:do file="g.Module_JSON" name="l.success" value="JSON_Output( l.json )" />

Load Custom Fields Efficiently

To ensure the best page performance, we recommend loading custom fields in the following order; as your use-case allows:

  1. Load through Admin UI if possible
  2. Then load by Product ID1
    • For example:
      <mvt:item name="customfields" param="Read_Product_ID( l.settings:product:id, 'brand,short_desc', l.settings:product:customfield_values:customfields )" />
      
  3. Then load by Product Code1
    • For example:
      <mvt:item name="customfields" param="Read_Product_Code( l.settings:product:code, 'brand,short_desc', l.settings:product:customfield_values:customfields )" />
      

Avoid Legacy Modules & Methods

Using the following modules is considered an anti-pattern and should be avoided if possible. Attempt to refactored or replace these modules with more modern & maintained features ands you're likely to experience performance increases.

This is especially true when using functions that now exist within the core software.

Incorrect
<mvt:item name="toolkit" param="sassign|foo|bar" />
<mvt:item name="toolkit" param="vassign|foo|l.all_settings:bar" />
<mvt:item name="toolkit" param="mvassign|foo|'bar' $ '!'" />
<mvt:item name="ry_toolbelt" param="assign|g.foo|toupper('bar')" />
<mvt:item name="sebenzatools" param="var|foo|'bar'" />
Correct
<mvt:assign name="l.foo" value="'bar'" />
<mvt:assign name="l.foo" value="l.settings:bar" />
<mvt:assign name="l.foo" value="'bar' $ '!'" />
<mvt:assign name="l.foo" value="toupper('bar')" />
<mvt:assign name="l.foo" value="'bar'" />

  1. For stores with large amounts of custom fields (50-100+), there are times where it may be better to load all custom fields instead of individual ones. For example:

    <mvt:comment>Store has
    <mvt:item name="customfields" param="Read_Product_ID( l.settings:product:id, '', l.settings:product:customfield_values:customfields )" />`