Moving a template from Smarty
This page ports one small Smarty template to the template language, step by step, and ends with a checklist for your own templates. Each step was rendered on a development installation against the "Fit & Healthy" front page (node 89).
Exponential runs Smarty templates itself: a design can hold Smarty 5 templates (
.stpl) next to its.tpltemplates, with every operator, fetch function, template function, override rule and cache of the template system. If you would rather keep writing Smarty, see Smarty templates (.stpl) next to .tpl and Moving from .tpl to .stpl, which also converts templates in both directions.
Exponential runs Smarty templates itself: a design can hold Smarty 5 templates (
.stpl) next to its.tpltemplates, with every operator, fetch function, template function, override rule and cache of the template system. If you would rather keep writing Smarty, see Smarty templates (.stpl) next to .tpl and Moving from .tpl to .stpl, which also converts templates in both directions.
Contents
- The Smarty template
- Step 1: variables
- Step 2: the data comes from fetch
- Step 3: the loop, links, dates and escaping
- Checklist
- Related pages
The Smarty template
A list of the latest pages of a section, as it might look in Smarty. The PHP that renders it assigns $site and
$pages. This is Smarty, for comparison; it was not run here:
assign var=limit value=3
<h2>$site.name|escape</h2>
if $pages|@count == 0
<p>No pages yet.</p>
else
<ul>
foreach from=$pages item=page
<li class="cycle values='odd,even'"><a href="$page.url">$page.name|truncate:30|escape</a>
$page.published|date_format:'%Y-%m-%d'</li>
/foreach
</ul>
/ifStep 1: variables
{assign} becomes {def}. Several variables can be defined in one tag, on several lines. Instead of receiving $site
from PHP, the template fetches the node. escape becomes wash:
{def $site = fetch( 'content', 'node', hash( 'node_id', 89 ) )
$limit = 3}
<h2>$site.name|wash</h2>Output:
<h2>Fit & Healthy</h2>Step 2: the data comes from fetch
The list is a content/list fetch with the parent, the limit and the sort order. The count test uses the count
operator; there is no @ and no ==:
{def $site = fetch( 'content', 'node', hash( 'node_id', 89 ) )
$limit = 3
$pages = fetch( 'content', 'list', hash( 'parent_node_id', $site.node_id,
'limit', $limit,
'sort_by', array( 'published', false() ) ) )}
<h2>$site.name|wash</h2>
<p>$pages|count of $site.children_count pages</p>Output:
<h2>Fit & Healthy</h2>
<p>3 of 16 pages</p>Step 3: the loop, links, dates and escaping
The finished template. What changed:
{if $pages|@count == 0}becomes{if $pages|count|eq( 0 )}.{foreach from=$pages item=page}becomes{foreach $pages as $page}.{cycle values='odd,even'}becomes the loop parametersequence array( 'odd', 'even' ) as $row.- A link is the node's
url_aliaspassed throughezurl, which adds the siteaccess path and the quotes:href={$page.url_alias|ezurl}. truncate:30becomesshorten( 30 ), andescapebecomeswash(after shortening, so an entity is not cut).date_formatbecomesdatetime( 'custom', ... )on the object'spublishedtimestamp.{undef}at the end removes the variables; inside a full view template, variables made with{def}are removed at the end of the file anyway.
{def $site = fetch( 'content', 'node', hash( 'node_id', 89 ) )
$limit = 3
$pages = fetch( 'content', 'list', hash( 'parent_node_id', $site.node_id,
'limit', $limit,
'sort_by', array( 'published', false() ) ) )}
<h2>$site.name|wash</h2>
if $pages|count|eq( 0 )
<p>No pages yet.</p>
else
<ul>
foreach $pages as $page sequence array( 'odd', 'even' ) as $row
<li class="$row"><a href=$page.url_alias|ezurl>$page.name|shorten( 30 )|wash</a>
$page.object.published|datetime( 'custom', '%Y-%m-%d' )</li>
/foreach
</ul>
/if
undef $site $limit $pagesOutput:
<h2>Fit & Healthy</h2><ul> <li class="odd"><a href="/slash-quotes">Slash Quotes</a>
2026-10-17</li> <li class="even"><a href="/exponential-live">Tour</a>
2026-10-05</li> <li class="odd"><a href="/fit-healthy-site-info">Fit & Healthy site info - [...</a>
2026-08-31</li></ul>The list shows what was published last on a development installation when this page was built, so the names will differ on your site.
Checklist
| Smarty | Template language |
|---|---|
{assign var=x value=...}, {$x = ...} | {def $x = ...}, later {set $x = ...} |
{$a + $b}, {$a * 2} | {sum( $a, $b )}, {$a|mul( 2 )} |
==, !=, >, <, >=, <= | eq, ne, gt, lt, ge, le |
&&, ||, ! | and( ... ), or( ... ), not( ... ) |
|default:'x' | first_set( $var, 'x' ) |
|escape, |escape:'url' | |wash, |rawurlencode |
|truncate:n | |shorten( n ) |
|upper, |lower, |capitalize | |upcase, |downcase, |upword |
|date_format:'...' | |datetime( 'custom', '...' ) or |l10n( 'shortdate' ) |
|@count | |count |
{foreach from=$a item=i key=k} | {foreach $a as $k => $i} |
{foreachelse} | {if $a|count}...{else}...{/if} around the loop |
{cycle values='a,b'} | sequence array( 'a', 'b' ) as $s |
{section name=i loop=$a} | {foreach}, or {for 0 to ... as $i} for ranges |
{include file='x.tpl'} | {include uri='design:x.tpl'} |
{capture name=x} | {set-block variable=x} |
{extends}, {block} | overrides and the pagelayout |
{php} | an operator or fetch function in an extension |
{$smarty.get.x} | ezhttp( 'x', 'get' ), washed before output |
After porting, render the template with debug output on and read the warnings: an operator that does not exist and a
set on a variable that was never defined both fail quietly in the page (see
Smarty and the template language: the differences in depth).
Related pages
- Moving from Exponential templates (.tpl) to Exponential Smarty templates (.stpl) and Smarty templates (.stpl) next to .tpl: Smarty templates run by Exponential itself
- Previous: Smarty and the template language: the differences in depth
- Next: Moving from .tpl to .stpl
- Fetching nodes and lists
- URL operators
- Dates, numbers and localization
- Free documentation: this page is free to copy, translate and improve under the GNU FDL 1.3.
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.