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:
- Raw JSON — the default. The annotation value is parsed as JSON, pretty-printed, and shown in a read-only JSON viewer with syntax highlighting.
- Custom template — you provide a Go
html/templatethat 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:
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:
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.