#Functions and Modules
XQuery is a full programming language, not just a query syntax. You can define functions, organize them into modules, and build reusable libraries — much like writing class libraries in C#.
#Contents
#User-Defined Functions
#Basic Function Declaration
declare function local:format-price($amount as xs:decimal) as xs:string {
concat("$", format-number($amount, "#,##0.00"))
};
local:format-price(1234.5)
(: Result: "$1,234.50" :)C# equivalent:
static string FormatPrice(decimal amount) => $"${amount:#,##0.00}";#Function Signatures
declare function local:function-name(
$param1 as type1,
$param2 as type2
) as return-type {
(: function body — an XQuery expression :)
};-
local:is the namespace prefix for local functions (not imported from a module) -
Parameter types are optional but recommended
-
The return type is optional but recommended
-
The body is a single expression (no statements — XQuery is functional)
#Multiple Parameters
declare function local:price-with-tax(
$amount as xs:decimal,
$rate as xs:decimal
) as xs:decimal {
round($amount * (1 + $rate), 2)
};
local:price-with-tax(99.99, 0.08)
(: Result: 107.99 :)#Default Parameter Values
XQuery doesn't support default parameter values directly, but you can use function overloading:
(: Two-argument version :)
declare function local:format-price(
$amount as xs:decimal,
$currency as xs:string
) as xs:string {
switch ($currency)
case "USD" return concat("$", format-number($amount, "#,##0.00"))
case "EUR" return concat("€", format-number($amount, "#,##0.00"))
default return concat(format-number($amount, "#,##0.00"), " ", $currency)
};
(: One-argument version defaults to USD :)
declare function local:format-price(
$amount as xs:decimal
) as xs:string {
local:format-price($amount, "USD")
};#Function Features
#Recursive Functions
XQuery supports recursion naturally:
declare function local:factorial($n as xs:integer) as xs:integer {
if ($n <= 1) then 1
else $n * local:factorial($n - 1)
};
local:factorial(5)
(: Result: 120 :)Practical recursion — flattening a hierarchy:
declare function local:flatten-tree($node as element()) as element()* {
$node,
for $child in $node/*
return local:flatten-tree($child)
};#Higher-Order Functions
Functions can take functions as parameters and return functions:
declare function local:apply-to-all(
$items as item()*,
$fn as function(item()) as item()
) as item()* {
for $item in $items
return $fn($item)
};
local:apply-to-all((1, 2, 3), function($n) { $n * $n })
(: Result: 1, 4, 9 :)C# equivalent:
IEnumerable<T> ApplyToAll<T>(IEnumerable<T> items, Func<T, T> fn) =>
items.Select(fn);#Anonymous Functions (Lambdas)
let $double := function($n) { $n * 2 }
let $add := function($a, $b) { $a + $b }
return ($double(5), $add(3, 4))
(: Result: 10, 7 :)
C# equivalent: Func<int, int> double = n => n * 2;
#Inline Functions with Closure
Anonymous functions capture variables from their enclosing scope:
let $multiplier := 3
let $fn := function($n) { $n * $multiplier }
return $fn(5)
(: Result: 15 — $multiplier is captured :)#Modules
Modules organize XQuery code into reusable libraries, like C# class libraries or namespaces.
#Library Modules
A library module declares a namespace and exports functions:
(: file: pricing.xqm :)
module namespace pricing = "http://example.com/pricing";
declare function pricing:format-price($amount as xs:decimal) as xs:string {
concat("$", format-number($amount, "#,##0.00"))
};
declare function pricing:with-tax(
$amount as xs:decimal,
$rate as xs:decimal
) as xs:decimal {
round($amount * (1 + $rate), 2)
};
declare function pricing:discount(
$amount as xs:decimal,
$percent as xs:decimal
) as xs:decimal {
round($amount * (1 - $percent div 100), 2)
};#Importing Modules
Main queries import library modules:
import module namespace pricing = "http://example.com/pricing"
at "pricing.xqm";
for $product in //product
return <item>
<name>{ $product/name/text() }</name>
<price>{ pricing:format-price(xs:decimal($product/price)) }</price>
<with-tax>{ pricing:with-tax(xs:decimal($product/price), 0.08) }</with-tax>
</item>C# parallel:
using Example.Pricing;
// Now use Pricing.FormatPrice(), Pricing.WithTax(), etc.#Module Variables
Modules can also export variables:
module namespace config = "http://example.com/config";
declare variable $config:tax-rate := 0.08;
declare variable $config:currency := "USD";
declare variable $config:date-format := "[MNn] [D], [Y]";#The XQuery Prolog
Every XQuery file starts with an optional prolog that sets up the execution environment:
xquery version "4.0";
(: Namespace declarations :)
declare namespace app = "http://example.com/app";
declare default element namespace "http://www.w3.org/1999/xhtml";
(: Module imports :)
import module namespace pricing = "http://example.com/pricing" at "pricing.xqm";
(: Variable declarations :)
declare variable $base-url := "/";
declare variable $site-title := "My Site";
(: Option declarations :)
declare option output:method "html";
declare option output:indent "yes";
(: Function declarations :)
declare function local:page-title($title as xs:string) as xs:string {
concat($title, " — ", $site-title)
};
(: Main query body :)
<html>
<head><title>{ local:page-title("Home") }</title></head>
<body>{ ... }</body>
</html>Order matters: version → namespaces → imports → variables → options → functions → body.
#Common Patterns
#Utility Library
module namespace util = "http://example.com/util";
(: Safe string truncation :)
declare function util:truncate($s as xs:string, $max as xs:integer) as xs:string {
if (string-length($s) > $max)
then concat(substring($s, 1, $max - 3), "...")
else $s
};
(: Null-safe default :)
declare function util:default($value as item()*, $fallback as item()*) as item()* {
if (exists($value)) then $value else $fallback
};
(: Slugify a string :)
declare function util:slugify($s as xs:string) as xs:string {
lower-case(replace(normalize-space($s), "[^a-zA-Z0-9]+", "-"))
};#Data Access Layer
module namespace db = "http://example.com/db";
declare function db:get-product($id as xs:string) as element(product)? {
collection("products")//product[@id = $id]
};
declare function db:search-products($query as xs:string) as element(product)* {
collection("products")//product[
contains(lower-case(name), lower-case($query)) or
contains(lower-case(description), lower-case($query))
]
};
declare function db:products-by-category($cat as xs:string) as element(product)* {
collection("products")//product[@category = $cat]
};#HTML Template Module
module namespace tmpl = "http://example.com/templates";
declare function tmpl:page(
$title as xs:string,
$content as node()*
) as element(html) {
<html>
<head>
<meta charset="UTF-8"/>
<title>{ $title }</title>
<link rel="stylesheet" href="/css/style.css"/>
</head>
<body>
<header><h1>{ $title }</h1></header>
<main>{ $content }</main>
<footer><p>© 2026</p></footer>
</body>
</html>
};
declare function tmpl:product-card(
$product as element(product)
) as element(div) {
<div class="card">
<h2>{ $product/name/text() }</h2>
<p class="price">${ $product/price/text() }</p>
<p>{ $product/description/text() }</p>
</div>
};Usage:
import module namespace tmpl = "http://example.com/templates" at "templates.xqm";
tmpl:page("Products", (
for $p in //product
order by $p/name
return tmpl:product-card($p)
))This is remarkably similar to component-based web development — small, reusable templates composed into larger pages. The difference is that the "rendering engine" is XQuery, and the "data layer" is XML.