Merge Tags
Merge tags provide dynamic, interactive placeholders (such as client.name or invoice.amount) for document generation and email templates. Typing {{ opens an autocomplete dropdown at the cursor caret, and enabling the feature unlocks a slide-out Merge Tag sidebar panel in the menubar.
Merge tags are disabled by default. The trigger is fixed as {{; item values are normalized to the same double-braced form before display, insertion, and event emission.
Key Highlights
Fixed Trigger
Typing {{ instantly opens a filtered popup menu at the cursor position.
Grouped Slide-out Drawer
A right slide-out panel categorizes merge tags by custom group names (e.g. Client, Proposal, Billing).
Atomic Token Chips
Inserted tags render as non-editable inline tokens that delete cleanly as a single unit when pressing Backspace or Delete.
Safe HTML Storage
Content is saved as structured HTML data attributes (data-erag-merge-tag-value="client.name").
Configuration & Item Interface
Define merge tag items in init.mergeTags:
import { computed } from 'vue';
import type { EditorInit, MergeTagItem } from '@erag/text-editor-vue';
const mergeTagItems = computed<MergeTagItem[]>(() => [
{ value: '{{amit}}' },
{ value: '{{client.name}}', group: 'Client' },
{ value: '{{client.salutation}}', group: 'Client' },
{ value: '{{submission.date}}', group: 'Proposal' },
{ value: '{{proposal.number}}', group: 'Proposal' },
]);
const editorConfig = computed<EditorInit>(() => ({
mergeTags: {
enabled: true,
limit: 10,
items: mergeTagItems.value,
},
}));| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enables the feature for an object configuration. |
limit | number | 10 | Maximum autocomplete results. |
items | MergeTagItem[] | [] | Consumer-provided tags displayed by the UI. |
The package does not invent merge tags. Supply at least one item for the menubar entry, sidebar, and suggestions to have content.
Merge Tag Item Interface (MergeTagItem)
| Property | Type | Required | Description |
|---|---|---|---|
value | string | Yes | Visible and stored token value, normally supplied as {{client.name}}. A bare or single-braced value is normalized to double braces. |
group | string | Optional | Group name used to categorize tags in the right slide-out drawer (e.g. Client, Proposal). |
Sidebar Panel & Menubar Integration
When mergeTags.enabled is true, a Merge tag menu option is added to the menubar. Clicking it opens a right slide-out sidebar drawer organized by category groups.
If you specify an explicit menubar array in configuration, include 'merge-tags':
const editorConfig: EditorInit = {
menubar: ['file', 'edit', 'insert', 'format', 'merge-tags'],
mergeTags: {
enabled: true,
items: mergeTagItems.value,
},
};The sidebar keeps ungrouped items in a common list and renders named groups as separate sections. Clicking an item restores the saved editor selection and inserts the tag at that caret position.
Autocomplete controls
After typing {{, suggestions are filtered against both normalized values and group names. Starts-with matches appear before contains matches, and limit controls the maximum visible results.
ArrowDown/ArrowUpmoves through results and wraps.Home/Endjumps to the first or last result.Enter/Tabinserts the active result.Escapecloses the dropdown.- Backspace after a token or Delete before it removes the complete token and emits
merge-tag-remove.
Merge Tag Events
Listen to tag selection and removal events:
<script setup lang="ts">
import { shallowRef } from 'vue';
import {
Editor,
type MergeTagRemoveEvent,
type MergeTagSelectEvent,
} from '@erag/text-editor-vue';
const content = shallowRef('');
function handleTagSelect(event: MergeTagSelectEvent) {
console.log('Inserted merge tag:', event.item.value);
}
function handleTagRemove(event: MergeTagRemoveEvent) {
console.log('Removed merge tag:', event.item.value);
}
</script>
<template>
<Editor
v-model="content"
:init="editorConfig"
@merge-tag-select="handleTagSelect"
@merge-tag-remove="handleTagRemove"
/>
</template>HTML Output & Backend Replacement
Inserted merge tags render as atomic non-editable token chips in exported HTML:
<span
class="erag-merge-tag"
data-erag-merge-tag="true"
data-erag-merge-tag-value="{{client.name}}"
contenteditable="false"
>{{client.name}}</span
>Backend Placeholder Resolution Example (Laravel / PHP)
Before sending an email or rendering a PDF report, parse the stored HTML and replace only recognized merge-tag elements. Avoid a global string replacement because it also changes the token's data attribute and leaves stale chip markup behind.
$document = new DOMDocument();
$document->loadHTML(
'<meta charset="utf-8"><body>'.$template->body.'</body>',
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD,
);
$replacements = [
'{{client.name}}' => $client->name,
'{{client.salutation}}' => $client->salutation,
'{{consultant.name}}' => $consultant->name,
];
$xpath = new DOMXPath($document);
foreach ($xpath->query('//span[@data-erag-merge-tag="true"]') as $node) {
$token = $node->attributes?->getNamedItem('data-erag-merge-tag-value')?->nodeValue;
if ($token !== null && array_key_exists($token, $replacements)) {
$node->parentNode?->replaceChild(
$document->createTextNode($replacements[$token]),
$node,
);
}
}Serialize the body children after replacement and pass the result through your server-side HTML sanitizer. Only replace values from an application allowlist. Do not treat a stored tag as executable code or as an arbitrary object path.