Built-in Functions
XLFill includes 16 built-in functions that you can use directly in ${...} expressions. No registration needed — they’re available in every template.
How to read this guide
Section titled “How to read this guide”Every function below shows:
- Syntax — what the call looks like in a template cell
- Examples — typical inputs and outputs
- (Some entries) Walkthrough — a paragraph showing where you’d use it in a real report
If you’re new to expressions, start with the Expressions guide — it covers the grammar (${...}, dot access, ternary, etc.) that hosts these functions.
Looking for the big picture (commands + functions + Go code together)? The Beginner Tutorial walks through everything end-to-end.
Text functions
Section titled “Text functions”upper(s)
Section titled “upper(s)”Converts a string to uppercase.
${upper(e.Name)} → "ALICE JOHNSON"${upper(e.Department)} → "ENGINEERING"lower(s)
Section titled “lower(s)”Converts a string to lowercase.
${lower(e.Email)} → "alice@example.com"${lower(e.Status)} → "active"title(s)
Section titled “title(s)”Converts a string to title case (first letter of each word capitalized).
${title(e.Name)} → "Alice Johnson"${title("hello world")} → "Hello World"join(sep, items)
Section titled “join(sep, items)”Joins a slice of values into a single string with a separator.
${join(", ", e.Skills)} → "Go, Python, SQL"${join(" | ", e.Departments)} → "Engineering | Research"${join("\n", e.Notes)} → multi-line textUseful for combining list data into a single cell value.
Walkthrough — collapse skill lists into one cell
You have an employee record where Skills is []string{"Go", "Python", "SQL"}. You don’t want three rows per employee; you want all the skills in one cell. join(", ", e.Skills) gives you "Go, Python, SQL" in a single cell, comma-separated. Combine with formatNumber(len(e.Skills), 0) for a count, and you can write a header like "Skills (3)" alongside the list.
Formatting functions
Section titled “Formatting functions”formatNumber(value, decimals)
Section titled “formatNumber(value, decimals)”Formats a number with comma thousands-separators and a given number of decimal places.
${formatNumber(e.Salary, 0)} → "75,000"${formatNumber(e.Salary, 2)} → "75,000.00"${formatNumber(e.Rating, 1)} → "4.5"${formatNumber(-1234567.89, 2)} → "-1,234,567.89"decimals is an integer count of digits after the decimal point.
formatDate(value, layout)
Section titled “formatDate(value, layout)”Formats a date/time value using Go’s time layout syntax.
${formatDate(e.HireDate, "2006-01-02")} → "2024-03-15"${formatDate(e.HireDate, "January 2, 2006")} → "March 15, 2024"${formatDate(e.HireDate, "02/01/2006")} → "15/03/2024"${formatDate(e.CreatedAt, "2006-01-02 15:04:05")} → "2024-03-15 09:30:00"Uses Go’s reference time (Mon Jan 2 15:04:05 MST 2006) — see Go time package for layout options.
Walkthrough — date in the report header
Your data map has "reportDate": time.Now(). In your template, cell A1 reads:
Generated on ${formatDate(reportDate, "January 2, 2006 at 3:04 PM")}Output: Generated on May 21, 2026 at 4:18 PM. Same template, used daily, always shows the current date in a human-friendly format.
Null-safe functions
Section titled “Null-safe functions”coalesce(values…)
Section titled “coalesce(values…)”Returns the first non-nil, non-empty value from the arguments.
${coalesce(e.Nickname, e.FirstName, "Unknown")} → first non-empty value${coalesce(e.Phone, e.Mobile, "N/A")} → fallback chainLike SQL’s COALESCE — essential for handling optional fields without template errors.
ifEmpty(value, fallback)
Section titled “ifEmpty(value, fallback)”Returns fallback if value is nil, empty string, or zero value; otherwise returns value.
${ifEmpty(e.Department, "Unassigned")} → "Engineering" or "Unassigned"${ifEmpty(e.Notes, "-")} → notes text or "-"Simpler than coalesce when you have exactly one value and one fallback.
Aggregation functions
Section titled “Aggregation functions”These functions operate on slices and are particularly useful with jx:updateCell for summary rows, or in any cell that needs to aggregate collection data.
sumBy(items, field)
Section titled “sumBy(items, field)”Sums a numeric field across a slice of items.
${sumBy(employees, "Salary")} → 450000${sumBy(orders, "Total")} → 12750.50avgBy(items, field)
Section titled “avgBy(items, field)”Calculates the average of a numeric field across a slice.
${avgBy(employees, "Salary")} → 75000${avgBy(reviews, "Score")} → 4.2countBy(items, field)
Section titled “countBy(items, field)”Counts non-nil values of a field across a slice.
${countBy(employees, "Email")} → 15 (employees with email addresses)${countBy(orders, "ShippedDate")} → 8 (orders that have shipped)minBy(items, field)
Section titled “minBy(items, field)”Returns the minimum value of a numeric field across a slice.
${minBy(employees, "Salary")} → 45000${minBy(scores, "Value")} → 2.1maxBy(items, field)
Section titled “maxBy(items, field)”Returns the maximum value of a numeric field across a slice.
${maxBy(employees, "Salary")} → 120000${maxBy(scores, "Value")} → 9.8Walkthrough — totals row at the bottom of a loop
You’re rendering an employee table with jx:each(items="employees" var="e" lastCell="C5"). Right below the loop area (in row 6), you want a totals row.
| Cell | Value |
|---|---|
| A6 | ”Total” |
| B6 | ${formatNumber(sumBy(employees, "Salary"), 0)} |
| C6 | ${formatNumber(avgBy(employees, "Salary"), 0)} |
sumBy and avgBy work on the full data slice (not just the loop iteration’s e), so they’re safe to use outside the loop too. Place them where you want them in the template — the engine evaluates them once at that position.
Link and annotation functions
Section titled “Link and annotation functions”hyperlink(url, display)
Section titled “hyperlink(url, display)”Creates a clickable hyperlink in the cell.
${hyperlink("https://example.com", "Visit Site")}${hyperlink(e.ProfileURL, e.Name)}${hyperlink("#Summary!A1", "Jump to summary")}${hyperlink("mailto:" + e.Email, e.Name)}The cell shows the display text and links to the URL. Works in Excel, Google Sheets, and LibreOffice.
Walkthrough — clickable email links per row
Each employee row should show the name as a clickable mailto link. In cell A2:
${hyperlink("mailto:" + e.Email, e.Name)}When users click “Alice Johnson” in the output, Excel opens their mail client. Same idea works for ticket links (hyperlink("https://tickets.example.com/" + t.ID, "Ticket #" + t.ID)) or cross-sheet jumps (hyperlink("#Sheet2!A1", "Go to details")).
comment(text)
Section titled “comment(text)”Adds a cell comment (note) to the cell.
${comment("Reviewed by finance team")}${comment(e.Notes)}The comment appears as a hover tooltip on the cell. Useful for adding context without cluttering the visible data.
Internationalization
Section titled “Internationalization”t(key)
Section titled “t(key)”Translates a key using the i18n translation map provided via WithI18n. If the key isn’t found, the key itself is returned (so you can spot missing translations in the output).
${t("header.name")} → "Name" (en) or "Nombre" (es) or "Name" (de)${t("status.active")} → "Active" or "Activo" or "Aktiv"Setup in Go:
translations := map[string]string{ "header.name": "Name", "header.department": "Department", "header.salary": "Salary", "status.active": "Active", "status.inactive": "Inactive",}
xlfill.Fill("template.xlsx", "report.xlsx", data, xlfill.WithI18n(translations),)Build one template, generate reports in any language by swapping the translation map. No separate templates per language.
Combining functions
Section titled “Combining functions”Functions compose naturally in expressions:
${upper(ifEmpty(e.Department, "UNKNOWN"))}${formatNumber(sumBy(items, "Total"), 2)}${hyperlink(e.URL, title(e.Name))}${join(", ", e.Skills) + " (" + formatNumber(len(e.Skills), 0) + " skills)"}Custom functions
Section titled “Custom functions”Need something not covered here? Register your own functions:
xlfill.Fill("template.xlsx", "report.xlsx", data, xlfill.WithFunction("currency", func(args ...any) (any, error) { amount := args[0].(float64) code := args[1].(string) return fmt.Sprintf("%s %.2f", code, amount), nil }),)Use in templates: ${currency(e.Salary, "USD")} produces USD 75000.00.
What’s next?
Section titled “What’s next?”See how all commands work together:
Or check the full API reference: