Tag Reference Overview
APD uses a powerful template tag system that allows you to embed dynamic content, loop through data, create conditions, and interact with Business Central data directly in your HTML templates.
Tag Syntax
All APD tags use the following syntax:
<?tagname(parameters)?>
Where:
<?opens the tagtagnameis the tag function name(parameters)contains the tag parameters?>closes the tag
Tag Categories
APD tags are organized into the following categories:
| Category | Purpose | Examples |
|---|---|---|
| Data Tags | Display and manipulate data | field, filter, count |
| Display Tags | Control output formatting | field, calcdate, content |
| Control Flow Tags | Loops and conditions | loop, if, profile |
| Form Tags | Create interactive forms | key, action |
| Security Tags | Control access | profile, auth |
| Utility Tags | Helper functions | parm, setvalue, getvalue |
Tag Compilation
APD templates are compiled before execution:
- Compile Time: Tags are parsed and validated
- Run Time: Compiled templates execute with current data
- Caching: Compiled templates are cached for performance
Compilation Errors
If a tag has invalid syntax:
- Compilation fails
- Error message indicates location
- Template won't execute until fixed
Important Rules
No Nesting Tags
You cannot place a <??>tag inside another <??>tag. This is the most important syntax rule in APD templates.
<!-- WRONG - nested tags will NOT work -->
<?filter(Customer,"No.",<?parm(id)?>)?>
<?if(<?field(Customer,Balance)?>,'>', 0)?>
<?setvalue(Total,<?field(Item,"Unit Price")?>)?>
<!-- CORRECT - use expression functions instead -->
<?filter(Customer,"No.",Parm('id'))?>
<?if(Customer,Balance,'>',0)?>
<?setvalue(Total,field(Item,"Unit Price"))?>
Instead of nesting tags, use expression functions inside tag parameters. See Expression Functions below.
Quoting Table and Field Names
When a table or field name contains spaces or special characters (periods, parentheses, slashes, hyphens, etc.), you must enclose the name in double quotes.
<!-- Names without spaces or special characters - no quotes needed -->
<?open(Customer)?>
<?field(Customer,Name)?>
<?field(Item,Description)?>
<!-- Names WITH spaces or special characters - quotes required -->
<?open("Sales Header")?>
<?field(Customer,"No.")?>
<?field(Item,"Unit Price")?>
<?field(Customer,"Balance (LCY)")?>
<?filter("Sales Line","Document No.",field("Sales Header","No."))?>
<?open("Salesperson/Purchaser")?>
<?field(Customer,"E-Mail")?>
Rule of thumb: If the name contains anything other than letters, wrap it in double quotes.
Table and Field Numbers
Anywhere a tag accepts a Table or Field name, you can use the BC table number or field number instead. Production templates often mix both styles:
<!-- By name -->
<?open("Sales Header")?>
<?field("Sales Header","No.")?>
<!-- By number (Sales Header = 36, No. = 3) -->
<?open(36)?>
<?field(36,3)?>
<!-- The two forms work the same and can be mixed -->
<?open(Customer)?>
<?field(18,Name)?>
<?filter(18,1,Parm('custno'))?>
Names are easier to read; numbers are sometimes used for forms (the name="18:2" HTML attribute convention used by <?hidden?>/<?edit?>/APD_validate requires the numeric form).
Single-Quoted String Identifiers
For string identifiers (action codes, profile codes, parameter names, content names, memory keys, etc.) the convention is single quotes:
<?action('TASK.REGISTER.DAILY')?>
<?profile('CUSTOMER')?>
<?parse('_header.html')?>
<?parm('orderno')?>
<?content('about-company')?>
<?setvalue('Total',0)?>
<?getvalue('Total')?>
<?hasvalue('cart')?>
Quick Reference
Most Common Tags
| Tag | Purpose | Example |
|---|---|---|
field |
Display field value | <?field(Customer,Name)?> |
loop |
Iterate records | <?loop(Item,'-')?>...<?endloop(Item)?> |
if |
Conditional display | <?if(Customer,Balance,'>',0)?>...<?endif?> |
parm |
Get URL parameter | <?parm(id)?> |
open |
Open data table | <?open(Customer)?> |
filter |
Filter records | <?filter(Customer,"No.",10000)?> |
key |
Build URL query fragment for primary key (use inside <a href> / form action URLs only) |
<a href="?action=DETAIL&<?key(Customer)?>"> |
content |
Include content | <?content(article-name)?> |
action |
Link to action | <?action(MY-ACTION)?> |
Expression Syntax
Expressions live inside the parameter list of a tag — not at the tag-output level. .. is the string-concatenation operator (Lua-style), and + - * / are arithmetic operators. Expressions can call the expression functions like field(Table,Field), Parm('name'), no(), today(), etc.
<!-- Multiple field tags in HTML: just put literal text between them -->
<h2><?field(Customer,Name)?> - <?field(Customer,City)?></h2>
<!-- To evaluate an expression and OUTPUT the result, use <?calc?> -->
<?calc(field(Item,"Unit Price")*field(Item,Quantity))?>
<?calc(field(Item,"Unit Price")*field(Item,Quantity),'<Precision,2:2>')?>
<!-- Expressions in filter values: equality is the bare expression -->
<?filter(Customer,"No.",Parm('id'))?>
<?filter("Sales Line","Document No.",field("Sales Header","No."))?>
<!-- Building a filter value via concatenation -->
<?filter(Customer,Name,'*'..Parm('search')..'*')?>
<?filter("task subscriber",Email,'@'..userid()..'@example.com')?>
<!-- Storing a computed value with <?setvalue?> -->
<?setvalue(FullName,field(Customer,Name)..' ('..field(Customer,"No.")..')')?>
<!-- Combining inside a view string (passed to <?table?> / <?open?>) -->
<?table("Sales Line",'where("Document No."=0('..field("Sales Header","No.")..'))')?>
Common mistake:
<?field(Customer,Name)..' - '..field(Customer,City)?>is not valid —<?field?>is a tag, and tag outputs cannot be combined with... To concatenate field values into one output, either write them as separate tags with literal HTML between them, or wrap the expression in<?calc?>/<?setvalue?>.
Data Context
Opening Tables
Before using field tags, open the table:
<?open(Customer)?>
<?filter(Customer,"No.",10000)?>
<?findfirst(Customer)?>
<h1><?field(Customer,Name)?></h1>
Loop Context
Inside loops, the current record is automatically available:
<?loop(Item,'-')?>
<tr>
<td><?field(Item,"No.")?></td>
<td><?field(Item,Description)?></td>
<td><?field(Item,"Unit Price")?></td>
</tr>
<?endloop(Item)?>
Tag Parameters
Parameter Types
| Type | Description | Example |
|---|---|---|
| Table Name | Business Central table | Customer, "Sales Header" |
| Field Name | Field in table | Name, "No.", "Unit Price" |
| Literal Value | Fixed text/number | 'Hello', 100 |
| Expression | Calculated value | field(Item,Qty)*2 |
| Format String | Output format | <Precision,2:2> |
Optional Parameters
Many tags have optional parameters:
<!-- Basic field output -->
<?field(Customer,Name)?>
<!-- Field with format -->
<?field(Item,"Unit Price",'<Precision,2:2><Standard Format,0>')?>
<!-- Field with HTML encoding disabled -->
<?field(Item,Description,,false)?>
Combining Tags with Control Flow
Tags can be combined with control flow for complex logic. Note that the if tag uses its own parameters (table, field, operator, value) - NOT nested tags:
<?if(Customer,Balance,'>',1000)?>
<span class="warning"><?field(Customer,Balance)?></span>
<?else?>
<span><?field(Customer,Balance)?></span>
<?endif?>
Remember: each <?...?> tag stands on its own. You never place one inside another.
Tag Processing Order
- Template loaded
- Includes resolved (
<?include?>) - Opens and filters executed
- Loops iterate
- Conditions evaluated
- Fields resolved
- Output generated
Best Practices
Performance
- Open tables once, not in loops
- Use specific filters to limit data
- Avoid unnecessary nested loops
Readability
- Use consistent indentation
- Comment complex logic
- Group related tags
Security
- Use security profiles for sensitive data
- Validate user input
- Apply table-level security filters
Debugging Tags
Common Issues
| Problem | Cause | Solution |
|---|---|---|
| Empty output | No data found | Check filters |
| Wrong value | Field name typo | Verify field name |
| Error | Invalid tag syntax | Check parameters |
| No loop | Table not opened | Add <?open()?> |
Testing Tips
- Start simple, add complexity
- Test each section separately
- Check compile errors
- Verify data exists
Expression Functions
Since you cannot nest <??>tags, use these expression functions inside tag parameters to access dynamic values. String concatenation is done with .. (double dot, like Lua).
Available Functions
| Function | Description | Example |
|---|---|---|
field(Table,Field) |
Get field value | field(Customer,Name) |
Parm('name') |
Get URL/POST parameter | Parm('id') |
today() |
Current date | today() |
time() |
Current time | time() |
count(Table) |
Record count | count(Item) |
no() |
User's BC entity number | no() |
abs(n) |
Absolute value | abs(field(Item,Inventory)) |
calcdate(formula,date) |
Calculate date | calcdate('-1M',today()) |
getvalue(key) |
Get session memory value | getvalue('Total') |
sessionid() |
Current session ID | sessionid() |
Company() |
User's APD company code | Company() |
CompanyName() |
BC company name | CompanyName() |
Empty() |
Returns two single quotes (for filtering) | Empty() |
ping() |
Returns a single quote character | ping() |
Expression Examples
<!-- Use Parm() instead of nesting <?parm()?> -->
<?filter(Customer,"No.",Parm('custno'))?>
<!-- Use field() instead of nesting <?field()?> -->
<?filter("Sales Line","Document No.",field("Sales Header","No."))?>
<?setvalue(CustomerName,field(Customer,Name))?>
<!-- Use no() instead of nesting <?no?> -->
<?filter(Customer,"No.",no())?>
<!-- Use getvalue() for accumulation -->
<?setvalue(Total,getvalue('Total')+field("Sales Line",Amount))?>
<!-- String concatenation with .. -->
<?setvalue(FullName,field(Customer,Name)..' ('..field(Customer,"No.")..')')?>
<!-- Arithmetic expressions -->
<?calc(field(Item,"Unit Price")*field(Item,Quantity))?>