Skip to main content
Applies to BloodHound Enterprise and Community Edition When you select a node or relationship in Explore, BloodHound displays its properties in the Entity Panel. OpenGraph extensions can add custom sections to the panel for extension-specific context. Define custom content in the info object of an extension definition schema using static Markdown or dynamic Go templates. This page covers custom section structure, Markdown and template validation, and dynamic content options.

Custom Entity Panel content

Use the info object to define custom Entity Panel sections for node kinds and relationship kinds. When a user selects a node or relationship in Explore, BloodHound renders an Entity Panel with the specified accordion sections as defined by the info entries. Each info object is a map of section identifiers to rendered sections. Use stable identifiers for the keys so future schema updates can modify the same section without changing its identity.
Draft your content in a Markdown editor, then convert it to a single-line JSON string before adding it to the content field. Encode newlines as escaped \n sequences. This workflow applies to both static and dynamic content.
string
Stable section identifier. Must match ^[a-z0-9_-]{1,128}$, which allows lowercase letters, numbers, hyphens, and underscores.
string
Section title displayed in the Entity Panel accordion header.
integer
Positive integer (1 or higher) that controls the order of extension-defined sections. Lower values render first. Use 1 for the first extension-defined section.
string
Single-line JSON string containing the Markdown rendered in the section body. Encode line breaks as escaped \n sequences. Required when markdown is present. You can use static Markdown or a Go template that renders values for the selected entity.
Every Entity Panel starts with Object Information at position 0. This section lists all properties for the selected node or relationship. BloodHound renders additional info entries for the selected node’s primary kind or the selected relationship’s kind after Object Information. BloodHound orders those extension-defined sections by position, then title. If no info entries are defined for the selected kind, BloodHound still renders Object Information, but does not render additional extension-defined Entity Panel sections.

Markdown validation

When you upload an extension definition schema, BloodHound validates each Markdown field to ensure it does not contain unsafe or disallowed HTML.
Validation only checks the content; BloodHound stores your original Markdown without modifying it.
BloodHound validates Markdown in the following fields:

Supported Markdown

BloodHound supports CommonMark plus the following GitHub Flavored Markdown extensions:
  • Tables
  • Strikethrough
  • Task lists
  • Autolinks
  • Fenced code blocks (with an alphanumeric language hint)
You can also include inline HTML that is on BloodHound’s allowlist of safe elements and attributes, such as links and basic text formatting.

Rejected content

BloodHound rejects the upload when a Markdown field contains unsafe or disallowed HTML, including:
  • <script> tags and other executable content
  • Inline event-handler attributes, such as onclick
  • javascript: links
  • <iframe>, <style>, and similar elements
If any Markdown field fails validation, the entire extension definition schema upload fails.

Dynamic Entity Panel content

Most Entity Panel content can be ordinary Markdown. When a panel should adapt to the selected node or relationship, use a template expression to insert values or describe relationship endpoints. This guide is organized by authoring goal:
  • Read a value: Basic templates
  • Transform or reuse a value: Pipelines and variables
  • Include or omit content: Control flow
The panel’s section name, title, and position are static. Templates change the Markdown rendered inside a section; they do not create or remove accordion sections.
Best practiceGuard dynamic content before rendering it. See Guarding Dynamic Content.

Template expressions

The simplest template expression evaluates a value and inserts the result into the Markdown. Expressions use Go text/template syntax and are enclosed in {{ and }}:
For a node whose name property is Alice, the rendered content is:
This small expression is the foundation for the rest of the guide. Static Markdown remains static (defined in advance). Use templates when content needs to be evaluated at render time (for example, to read entity data, compute or reuse a value, or conditionally render part of the Markdown).

Basic templates

Template expressions evaluate values available to the template and insert their results into Markdown. Those values may come from the current graph context, such as .Properties, .Source, or .Target, or from template variables declared with :=. The surrounding Markdown remains literal, while the template determines which values are evaluated and where their results appear.
  • Graph context values come from the selected node or relationship and are accessed from ..
  • Template variables are local names created with :=, such as $name or $value. They store a value so the template can reuse it.
Graph context describes the data the panel starts with. Template variables describe values that the template creates and reuses while rendering.

Graph context

The graph context is the entity data made available to the panel. Its shape depends on whether the panel is rendered for a node or a relationship.
Node context
For a node panel, the root context represents the selected node: Template example:
Rendered example:
Relationship context
For a relationship panel, the root context represents the relationship and includes source and target node contexts: Example:
Rendered example:
The context is intentionally limited. Templates cannot fetch additional data, traverse the graph, or access the complete internal node or relationship object.
Direct Property References and get
Use a direct property reference when the key is a normal identifier, such as .Properties.displayName. Use get when you need a string-based map lookup—for example, when a key contains punctuation or spaces, or when the key is stored in another template variable:
get only looks up a key in the .Properties map; it does not retrieve additional graph data or make absent properties available.

Function syntax

Go template functions use space-separated call syntax: the function name comes first, followed by its arguments. Parentheses can group an argument and nest a function call, but they are not written as conventional function-call syntax:
Use parentheses when a function result must be passed as an argument to another function or when a condition combines multiple comparisons:
Use pipelines for straightforward left-to-right transformations. A function receives graph context expressions, template variables, literal values, or parenthesized expressions and returns a result. Assign the result with := when it needs to be reused.

Pipelines

Go template pipelines use | to pass the result of one command to the next command as its final argument:
Functions with arguments can be used in pipelines. In Go templates, the result of the preceding command is appended as the final argument to the next command, so place fixed arguments before the piped value:
Here, replace receives its fixed arguments (" " and "-") before the piped property value. If the piped value belongs in another position, use a direct function call and pipe its result instead. For example, get expects the map before the key:

Whitespace trimming

Hyphens next to the template brackets are optional. They trim whitespace around an action:
  • {{- trims whitespace immediately before the action.
  • }} trims whitespace immediately after the action.
  • {{- ... -}} trims whitespace on both sides.
Whitespace includes spaces, tabs, and newlines. Hyphens do not represent subtraction and do not change the value being evaluated. Trimming is common around control-flow actions because a block that renders no content can otherwise leave an unwanted blank line. Without trimming:
When isTierZero is false, the rendered content contains an empty line:
With trimming around the control-flow actions:
When isTierZero is false, the rendered content is:
Use trimming deliberately. It can remove meaningful spacing that may be intended. For example:
Renders as Access: admin, while:
Renders as Access:admin.

Template variables

Template variables are local names that start with $ and are assigned with :=. A variable declared in the current template body remains available through the end of that template execution. A variable declared inside an if, range, or with block is available only within that block. Each markdown.content value is rendered independently, so variables do not carry between separate Entity Panel objects.
Reusable variables
Create a local template value by writing a $-prefixed identifier first, followed by := and the expression that supplies its value: $identifier := expression. Reuse that value later by referencing the same identifier, such as $edgeType. In the example below, each value is assigned once and reused in multiple rendered sentences or list items.
Rendered example:

Control flow

The if, else, end, range, and with elements are Go template actions (not helper functions) that control if and how parts of the Markdown content are displayed.

Required versus optional syntax

The following structural elements are required or optional as follows:
  • if starts a conditional block.
  • end closes an if, range, or with block.
  • else is optional and provides the alternate branch of an if block.
  • else if is optional and adds another conditional branch before the final else or end.
  • range starts an iteration block.
  • with starts a block that changes the current context when a value is present.
  • Hyphens next to the brackets are optional whitespace controls. They are not required for any action.
An opening if action requires one or more arguments that evaluate to boolean values. Use conditional checks to produce those values. The following example shows an if action without an else or else if branch:
An else action does not take arguments; it renders the alternate branch when the if condition is false. See Conditional content below for an example. An else if action accepts its own arguments (like if) and evaluates them only when the preceding condition is false. Use else if to add another conditional branch before an optional else action.

Conditional content

A conditional can wrap a sentence, paragraph, list, code block, or complete Markdown subsection. This is useful when the same guidance needs different explanatory or action-oriented copy depending on the selected entity’s state. For example, security guidance for an entity could present different Kerberos-delegation options for service accounts and non-service accounts. Protected Users might be preferred for non-service accounts, while service accounts may not be compatible with Protected Users; in that case, mark the account as sensitive instead. The property name below is illustrative; use the property exposed by the extension’s entity data. The following example shows an if/else pair:
For a service account, the rendered content is:
For a non-service account, the rendered content is:
This is a true either/or rendering decision: exactly one branch is emitted. The conditional can remove the section’s content, but it cannot remove the accordion itself. The section remains because its structure is statically defined in the extension definition schema. Use else when the reader needs alternate wording. Use with when a block should appear only when a value is present; inside the block, . becomes that value. Use range to render a collection, such as a list of tags.

Supported functions

Functions are separate from Go template actions. The Entity Panel function map is a curated Sprig-based allowlist. Template actions: The function map excludes functions that are unsafe, expensive, or inappropriate for Entity Panel authoring, including regular-expression, cryptographic, certificate-generation, random-number, URL-parsing, and URL-joining helpers. The supported-function reference is the source of truth for the target release.

Errors and testing

If a template cannot be parsed or executed, the response retains the original unexecuted template string and includes a template error describing the failure. Schema validation should catch many invalid syntax errors, but not all errors are known prior to runtime resolution. Common causes include:
  • Misspelled context fields or properties
  • Unsupported functions
  • Invalid template syntax
  • Missing values without fallback logic
  • Values passed to a helper with an unexpected type
Test templates incrementally:
  1. Start with static Markdown.
  2. Add one value expression.
  3. Add a fallback with default.
  4. Add a pipeline or reusable variable.
  5. Add control flow and test every branch.
  6. Inspect rendered Markdown, including whitespace.

Quick checklist

Before publishing a dynamic Entity Panel: