> ## Documentation Index
> Fetch the complete documentation index at: https://puzzlet-9ba7bb98.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Tags

> Use built-in and custom tags in TemplateDX to control template flow and transform data.

Tags are JSX elements backed by a plugin. TemplateDX ships five built-in tags (`<If>`, `<ElseIf>`, `<Else>`, `<ForEach>`, `<Raw>`) and exposes two ways to register your own: a global static API and a per-instance API on `TemplateDX`.

## Creating custom tags (TypeScript)

### Extend `TagPlugin`

Create a class that extends `TagPlugin` and implements `transform`:

```typescript theme={null}
import { Node } from 'mdast';
import { TagPlugin, PluginContext } from '@agentmark-ai/templatedx';

interface MyTagProps {
  prefix?: string;
}

class MyTagPlugin extends TagPlugin<MyTagProps> {
  async transform(
    props: MyTagProps,
    children: Node[],
    context: PluginContext
  ): Promise<Node[] | Node> {
    const { nodeHelpers } = context;

    const content = nodeHelpers.toMarkdown({
      type: 'root',
      children: children,
    });

    const prefix = props.prefix ?? '> ';
    const prefixedContent = content
      .split('\n')
      .map(line => prefix + line)
      .join('\n');

    return [{
      type: 'text',
      value: prefixedContent,
    } as Node];
  }
}
```

### The transform method

`transform(props, children, context)` must return a `Promise<Node | Node[]>`.

`context: PluginContext` exposes:

| Field                   | Type                    | Description                                                                                                |
| ----------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------- |
| `nodeHelpers`           | `NodeHelpers`           | AST utilities (see below)                                                                                  |
| `scope`                 | `Scope`                 | Current variable scope; read via `scope.get(key)`, create a child with `scope.createChild({ key: value })` |
| `createNodeTransformer` | `(scope: Scope) => any` | Factory for recursively transforming child nodes under a new scope                                         |
| `tagName`               | `string`                | Name of the tag the transformer is processing; the transformer sets it from `node.name`                    |

<Note>
  `tagName` has type `string`, but the conditional plugin (`tag-plugins/conditional.ts`) defensively guards `if (!tagName) throw ...`. Built-in tags can rely on `tagName` having a value; advanced consumers that invoke a plugin's `transform` directly (outside the transformer) should pass a valid name.
</Note>

#### nodeHelpers surface

| Member                      | Purpose                                                                                      |
| --------------------------- | -------------------------------------------------------------------------------------------- |
| `isMdxJsxElement(node)`     | `true` for flow or text JSX elements                                                         |
| `isMdxJsxFlowElement(node)` | `true` for block-level JSX                                                                   |
| `isMdxJsxTextElement(node)` | `true` for inline JSX                                                                        |
| `isParentNode(node)`        | `true` if the node has a `children` array                                                    |
| `toMarkdown(node)`          | Serialize an AST node back to Markdown/text                                                  |
| `hasFunctionBody(node)`     | `true` for expression nodes containing an arrow function (for example, `<ForEach>` callback) |
| `getFunctionBody(node)`     | Returns `{ body, argumentNames }` for arrow-function expression nodes                        |
| `NODE_TYPES`                | Constants for AST node-type strings (`TEXT`, `PARAGRAPH`, `MDX_JSX_FLOW_ELEMENT`, …)         |

### Register the plugin (static or instance API)

The registry exposes **both** a static API (process-wide) and an instance API (scoped to a `TemplateDX` engine).

**Static (global) registration** is the simplest path; everything using the default `transform`/`stringify` exports sees it:

```typescript theme={null}
import { TagPluginRegistry } from '@agentmark-ai/templatedx';

TagPluginRegistry.register(new MyTagPlugin(), ['MyTag', 'Prefix']);
```

**Instance (scoped) registration** is for when you want plugins isolated per engine (for example, a server handling multiple tenants with different tag sets):

```typescript theme={null}
import { TemplateDX } from '@agentmark-ai/templatedx';

const engine = new TemplateDX({ includeBuiltins: true });
engine.registerTagPlugin(new MyTagPlugin(), ['MyTag']);

const rendered = await engine.transform(ast, { /* props */ });
```

`new TemplateDX({ includeBuiltins: true })` copies the static built-ins (`If`, `ElseIf`, `Else`, `ForEach`, `Raw`) into the instance; pass `false` to start empty.

### Use the tag in a template

```tsx theme={null}
<MyTag prefix="// ">
  This is some content
  that will be prefixed
</MyTag>
```

### Example: `Quote` tag

```typescript theme={null}
import { Node, Root } from 'mdast';
import { TagPlugin, PluginContext, TagPluginRegistry } from '@agentmark-ai/templatedx';

interface QuoteProps {
  author?: string;
}

class QuotePlugin extends TagPlugin<QuoteProps> {
  async transform(
    props: QuoteProps,
    children: Node[],
    context: PluginContext
  ): Promise<Node[]> {
    const { nodeHelpers } = context;

    // toMarkdown returns a trailing newline; trim it so the
    // line-prefix map doesn't produce an empty filler line.
    const content = nodeHelpers.toMarkdown({
      type: 'root',
      children: children,
    } as Root).trimEnd();

    let result = content
      .split('\n')
      .map(line => '> ' + line)
      .join('\n');

    if (props.author) {
      result += `\n> \n> -- ${props.author}`;
    }

    return [{
      type: 'text',
      value: result,
    } as Node];
  }
}

TagPluginRegistry.register(new QuotePlugin(), ['Quote']);
```

Usage:

```tsx theme={null}
<Quote author="Albert Einstein">
  Imagination is more important than knowledge.
</Quote>
```

Output:

```text theme={null}
\> Imagination is more important than knowledge.
\> 
\> -- Albert Einstein
```

The final `stringify` step backslash-escapes line-leading `>` in text nodes (the same mechanism that escapes `{` and `<`), so blockquote markers your plugin emits appear escaped in the rendered output.

## Creating custom tags (Python)

`agentmark-templatedx` (Python) mirrors the TS surface. Subclass `TagPlugin`, implement `async def transform`, and register via the static (`register_global`) or instance API.

```python theme={null}
from templatedx import TagPlugin, TagPluginRegistry
from templatedx.tag_plugin import PluginContext

class QuotePlugin(TagPlugin):
    async def transform(self, props, children, context: PluginContext):
        content = context.node_helpers.to_markdown(children)
        author = props.get("author")
        body = "\n".join(f"> {line}" for line in content.split("\n"))
        if author:
            body += f"\n> \n> -- {author}"
        return [{"type": "text", "value": body}]

# Global (static) registration
TagPluginRegistry.register_global(QuotePlugin(), ["Quote"])
```

The Python `PluginContext` dataclass has `node_helpers` (snake\_case mirror of the TS `NodeHelpers` surface), `create_node_transformer`, `scope`, and `tag_name`.

For instance-scoped registration, construct the engine and use `register_tag_plugin`:

```python theme={null}
from templatedx import TemplateDX

engine = TemplateDX()  # always copies global built-ins on init
engine.register_tag_plugin(QuotePlugin(), ["Quote"])

result = await engine.transform(ast, {"name": "Alice"})
```

## Built-in tags

### `ForEach`

The `ForEach` tag loops over an array.

**Syntax**

```tsx theme={null}
<ForEach arr={props.arr}>
  {(item, index) => ...}
</ForEach>
```

**Parameters**

* `arr: Array<T>`: an array of items you want to iterate on
* `children: (item: T, index: number) => any`: a callback function for each item

**Example**

```tsx theme={null}
<ForEach arr={[1, 2]}>
  {(item, index) => (
    <>
      * item: {item}, index: {index}
    </>
  )}
</ForEach>
```

**Output**

```text theme={null}
* item: 1, index: 0
* item: 2, index: 1
```

### Conditionals

The `If`, `ElseIf`, and `Else` tags let you conditionally output content.

**Syntax**

```tsx theme={null}
<If condition={props.boolA}>
  ...
</If>
<ElseIf condition={props.boolB}>
  ...
</ElseIf>
<Else>
 ...
</Else>
```

**Parameters**

`If` / `ElseIf`:

* `condition: boolean`: the condition to check
* `children: Node`: the node to render if the condition is true

`Else`:

* `children: Node`: the content to render if no previous condition matched

<Warning>
  `condition` must be an actual boolean. Non-boolean values coerce to `false`, not truthy: `<If condition={props.list}>` renders nothing even when the list is non-empty. Pass a comparison instead, such as `condition={props.list.length > 0}`.
</Warning>

**Example**

```tsx theme={null}
<If condition={1 + 1 == 3}>
  1 + 1 is not 3
</If>
<ElseIf condition={1 + 1 == 2}>
  1 + 1 is 2
</ElseIf>
<Else>
  Fallback
</Else>
```

**Output**

```text theme={null}
1 + 1 is 2
```

<Warning>
  A scope supports one condition chain. After any `<If>`, `<ElseIf>`, or `<Else>` in a scope has rendered, every later conditional tag in that same scope renders nothing, even a new `<If>` with a true condition. Conditionals nested inside another tag's children (for example a `<ForEach>` or `<If>` body) get a fresh scope, so this limit doesn't affect them.
</Warning>

### Raw

The `Raw` tag outputs its children verbatim, without expression interpolation.

**Syntax**

```tsx theme={null}
<Raw>
  ...
</Raw>
```

**Parameters**

* `children: Node`: the raw text

**Example**

```tsx theme={null}
<Raw>
  {props.name}
</Raw>
```

**Output**

```text theme={null}
\{props.name}
```

## Next steps

Tags handle control flow; for value transformation inside expressions, see [Filters](/templatedx/filters).
