Skip to main content
Custom columns allow you to display additional host information in the overview table by extracting data from Ansible facts using jsonxs expressions or Mako templates.
Custom columns are currently only supported by the html_fancy and html_fancy_split templates.

Overview

The -C (--cust-cols) option takes a path to a Python file containing custom column definitions. Each column can use either:
  • jsonxs expressions: Simple path-based fact lookups
  • Mako templates: Advanced rendering with full Python logic

Column Definition Structure

Custom column files use Python syntax with a list of dictionaries:
[
    {
        "title": "Column Title",
        "id": "unique_id",
        "sType": "string",
        "visible": True,
        "jsonxs": "ansible_facts.some_fact"
    }
]

Required Fields

FieldDescriptionValues
titleUser-friendly column headerAny string
idUnique column identifierLowercase, underscores
sTypeHow values are sorted"string" or "num"
visibleShow by defaultTrue or False

Content Fields (one required)

FieldDescription
jsonxsPath to fact value (e.g., ansible_facts.ansible_apparmor.status)
tplMako template fragment with access to host variable

Using jsonxs Expressions

jsonxs provides simple dot-notation access to nested fact data:
[
    # Show whether AppArmor is enabled
    {
        "title": "AppArmor",
        "id": "apparmor",
        "sType": "string",
        "visible": False,
        "jsonxs": "ansible_facts.ansible_apparmor.status"
    }
]

Finding jsonxs Paths

1

Locate a fact file

Open any gathered fact file from your out/ directory in a JSON editor
2

Navigate to the value

Find the data you want to display in the nested structure
3

Build the path

Join the keys with dots: ansible_facts.ansible_dns.nameservers

Using Mako Templates

Mako templates provide full Python logic for complex rendering:
[
    # Show the nameservers configured on the host
    {
        "title": "Nameservers",
        "id": "nameservers",
        "sType": "string",
        "visible": True,
        "tpl": """
          <ul>
            <%
            # Get ansible_facts.ansible_dns.nameservers
            facts = host.get('ansible_facts', {})
            dns = facts.get('ansible_dns', {})
            nameservers = dns.get('nameservers', [])
            %>
            % for nameserver in nameservers:
              <li>${nameserver}</li>
            % endfor
          </ul>
        """
    }
]

Template Variables

VariableDescription
hostComplete host dictionary with all facts
jsonxsFunction for safe fact access

Safe Data Access

Always use .get() with defaults or the jsonxs() function. If any host is missing the data you’re accessing, ansible-cmdb will crash with a KeyError.
facts = host['ansible_facts']
nameservers = facts['ansible_dns']['nameservers']

Complete Example

Here’s a real example from the ansible-cmdb repository:
example/cust_cols.conf
[
    # Show whether AppArmor is enabled
    {
        "title": "AppArmor",
        "id": "apparmor",
        "sType": "string",
        "visible": False,
        "jsonxs": "ansible_facts.ansible_apparmor.status"
    },
    # Show the nameservers configured on the host
    {
        "title": "Nameservers",
        "id": "nameservers",
        "sType": "string",
        "visible": True,
        "tpl": """
          <ul>
            <%
            # Get ansible_facts.ansible_dns.nameservers
            facts = host.get('ansible_facts', {})
            dns = facts.get('ansible_dns', {})
            nameservers = dns.get('nameservers', [])
            %>
            % for nameserver in nameservers:
              <li>${nameserver}</li>
            % endfor
          </ul>
        """
    },
    # Show the nameservers configured on the host, but use jsonxs.
    {
        "title": "Nameservers2",
        "id": "nameservers2",
        "sType": "string",
        "visible": True,
        "tpl": """
          <ul>
            <%
            # Get ansible_facts.ansible_dns.nameservers using jsonxs
            nameservers = jsonxs(host, 'ansible_facts.ansible_dns.nameservers', default=[])
            %>
            % for nameserver in nameservers:
              <li>${nameserver}</li>
            % endfor
          </ul>
        """
    }
]

Advanced: Sortable Columns

Numeric Sorting

For "sType": "num" columns, any non-numeric characters will break sorting.

String Sorting with Hidden Values

For human-friendly display with proper sorting, use hidden sort helpers:
{
    "title": "Uptime",
    "id": "uptime",
    "sType": "string",
    "visible": False,
    "tpl": """
      <%
      def ConvertSectoDay(n):
          weeks = n // (7 * 24 * 3600 )
          n = n % (7 * 24 * 3600)
          days = n // (24 * 3600)
          n = n % (24 * 3600)
          hours = n // 3600
          n %= 3600
          minutes = n // 60
          n %= 60
          seconds = n

          return("%d Weeks, %d Days, %d Hours, %d Minutes, %d Seconds" % (weeks, days, hours, minutes, seconds))

      num_uptime = int(jsonxs(host, 'ansible_facts.ansible_uptime_seconds', default=0))
      sort_uptime = "{0:012d}".format(num_uptime)
      uptime = ConvertSectoDay(int(jsonxs(host, 'ansible_facts.ansible_uptime_seconds', default=0)))
      %>
      ## hidden sort helper
      <span style="display:none">${sort_uptime}</span>
      <span>${uptime}</span>
    """
}
String sorting considers “100” smaller than “20” since “1” < “2”. Use zero-padding or map values to a 0.0-1.0 range for correct sorting.

Usage

Apply your custom columns file:
ansible-cmdb -C example/cust_cols.conf -i hosts out/ > cmdb.html
If new columns don’t show up or you experience strange behavior:
  1. Open the generated HTML in your browser
  2. Click the Clear settings button in the top-right
  3. Refresh the page
This clears localStorage settings that may be caching old column configurations.

See Also

Build docs developers (and LLMs) love