Skip to content

Custom Content

swcat can display custom information on entity detail pages based on entity annotations or status observations. This is useful for integrating external data or surfacing metadata that does not fit into the standard catalog schema. It is also the primary way to visualize data generated by Sidecar Extensions.

How it works

You configure swcat to look for specific annotations (or status observations) on your entities and render their values as dedicated sections in the user interface. Each section becomes one collapsible <details> card on the entity detail page.

A section can be rendered in one of two ways:

  1. Raw JSON — the default. The annotation value is parsed as JSON, pretty-printed, and shown in a read-only JSON viewer with syntax highlighting.
  2. Custom template — you provide a Go html/template that produces semantic HTML. This gives you full control over the layout (tables, lists, key-value pairs, links, …).

1. Configure swcat.yml

In your swcat.yml, use ui.annotationBasedContent (or ui.statusBasedContent) to define one section per annotation/observation key. The minimal configuration just declares the heading and lets the value render as raw JSON:

ui:
  annotationBasedContent:
    # Key is the annotation name.
    my-org.com/data:
      heading: Organization Data
      open: true   # If true, the section is expanded on load.
      rank: 10     # Optional: lower ranks appear first.

2. Annotate your entities

Add the corresponding annotation to your entity metadata. The value is a string from YAML's perspective; swcat parses it as JSON when interpreting it for custom content.

apiVersion: swcat.dnswlt.me/v1alpha1
kind: Component
metadata:
  name: my-service
  annotations:
    my-org.com/data: |
      {
        "team": "Alpha",
        "cost-center": "12345",
        "criticality": "high"
      }
spec:
  type: service

Custom Templates

To render the value as anything other than raw JSON, set a template. The template's data (.) is the JSON-decoded value — typically a map, slice, string, number, or boolean. If the value isn't valid JSON, the raw string is passed instead.

ui:
  annotationBasedContent:
    my-org.com/data:
      heading: Organization Data
      template: |
        <table>
          <tr><th>Team</th><td>{{ .team }}</td></tr>
          <tr><th>Cost Center</th><td>{{ .["cost-center"] }}</td></tr>
          <tr><th>Criticality</th><td>{{ .criticality }}</td></tr>
        </table>

Templates use Go's html/template package, which auto-escapes any data inserted via {{ … }}. Raw HTML in the template literal itself (<table>, <a href="…">, etc.) is rendered as-is.

Loading templates from a file

For larger templates that you'd rather not embed in YAML, use templateFile with a path relative to the directory of the config file:

ui:
  annotationBasedContent:
    asyncapi.com/channels:
      heading: AsyncAPI Channels
      templateFile: custom/asyncapi_channels.html

template and templateFile are mutually exclusive — set one or the other, not both.

The template's output is wrapped in a <div class="custom-content">…</div>. Use semantic HTML; styling for the contents of .custom-content is provided by swcat's stylesheet — there's no need to know about Tailwind or any utility classes.

The following elements have explicit styling and are the recommended building blocks for custom content:

Element Use for
<h2>, <h3>, <h4> Sub-headings within the section.
<p> Paragraphs of text.
<ul>, <ol>, <li> Bulleted or numbered lists.
<dl>, <dt>, <dd> Key/value pairs (rendered as a 2-column grid).
<table>, <thead>, <tbody>, <tr>, <th>, <td> Tabular data.
<a> Links.
<code>, <pre> Inline and block code.
<kbd> Keyboard input.
<strong>, <em> Emphasis.
<hr> Horizontal rule.
<blockquote> Quotations or callouts.

Other elements will render with whatever default styling the browser applies after Tailwind preflight — typically very plain. Stick to the list above for a consistent look.

Template helpers

The following helper functions are available in custom-content templates:

Helper Description
join Format a slice or array as a comma-separated string (e.g. {{ .tags \| join }}).

Examples

List

ui:
  annotationBasedContent:
    solace.com/topics:
      heading: Solace Topics
      template: |
        <ul>
          {{ range . }}<li>{{ . }}</li>{{ end }}
        </ul>

Annotation:

metadata:
  annotations:
    solace.com/topics: |
      ["users/signup", "users/login", "orders/created"]

Table

ui:
  annotationBasedContent:
    asyncapi.com/channels:
      heading: AsyncAPI Channels
      template: |
        <table>
          <thead><tr><th>Topic</th><th>Messages</th></tr></thead>
          <tbody>
            {{ range . }}
            <tr>
              <td>{{ .address }}</td>
              <td>{{ .messages | join }}</td>
            </tr>
            {{ end }}
          </tbody>
        </table>

Annotation:

metadata:
  annotations:
    asyncapi.com/channels: |
      [
        { "address": "users/signup", "messages": ["UserSignedUp"] },
        { "address": "users/login",
          "messages": ["UserLoggedIn", "UserLoginFailed"] }
      ]

Raw JSON

Omit template entirely to get the read-only JSON viewer:

ui:
  annotationBasedContent:
    swcat/custom-fields:
      heading: Deployment Details

Status-based content

ui.statusBasedContent works exactly the same way, but the keys refer to status observations rather than annotations. The section additionally shows a small footer with updatedAt (and version, if present) for the observation.

ui:
  statusBasedContent:
    swcat/plugins-update-time:
      heading: Plugins Update Time
      open: true
      template: |
        <p>{{ . }}</p>