Traven

Host Integration Guide

Recommended practices for integrating the Traven editor into host applications, CMS systems, and administrative interfaces.

1. Choosing Your API: Web Component vs. Programmatic JS

Traven provides two equally supported APIs to integrate the editor depending on your application's architecture:

A. The <traven-editor> Web Component (Declarative)

  • Best for: Server-rendered forms (PHP, Django, Laravel, Rails) and quick drop-in static HTML pages.
  • How it works: Loading traven.js automatically registers the <traven-editor> custom element. You write it directly in your HTML templates, and it behaves exactly like a native <textarea>, utilizing formAssociated properties to seamlessly submit its value with your forms.

B. The TravenEditor Class (Programmatic)

  • Best for: Client-side reactive frameworks (Alpine.js, React, Vue, Svelte) and advanced JS integrations requiring fine-grained lifecycle or event handling.
  • How it works: You instantiate the class in JavaScript with new TravenEditor({ element, ... }). This allows you to mount the editor inside any generic HTML container (like a <div>) and reactively bind to its callbacks (such as onChange or onSave).

NOTE: Under the hood, the <traven-editor> Web Component is just a wrapper around the TravenEditor JavaScript class. You can choose whichever API fits best with your host environment.


2. YAML Frontmatter & Metadata Management

Writers often use YAML frontmatter blocks at the beginning of Markdown files to manage structured metadata (e.g. titles, tags, publish dates, authors).

When integrating Traven, you have two architectural approaches for managing this frontmatter:

Approach A: Freeform Editing (Inside the Editor)

  • Best for: Personal wikis, Obsidian-like environments, or power-user developer tools where authors prefer editing raw metadata manually in text format.
  • How it works: Pass the entire raw Markdown file (including the frontmatter boundaries ---) directly to Traven. Traven detects the frontmatter block, formats it with a monospace gray left-rail design, and collapses the boundaries when the editor is unfocused.
// Approach A: Pass the entire raw file directly
const editor = new TravenEditor({
  element: document.getElementById("editor-mount"),
  initialValue: data.content // Includes raw frontmatter block
});

Approach B: Structured Forms (Split-Before / Join-After) — Recommended for CMSs

  • Best for: Corporate CMSs, headless platforms, and databases where metadata must be typed into structured input forms (e.g. date-pickers, tag dropdowns, author select elements) and validated against a schema (like Pydantic, Laravel Request validation, etc.).
  • Why it is recommended: Letting users edit YAML directly as freeform text inside the editor introduces the risk of schema corruption (e.g. malformed spaces, incorrect keys, syntax errors). By splitting the frontmatter, the host application manages metadata validation, and the editor focuses entirely on body Markdown.

1. JavaScript Splitting/Joining Recipe

Add these helper utilities to your host CMS frontend bundle. The regex pattern handles both standard Linux/macOS (\n) and Windows (\r\n) line endings, and matches even if the closing delimiter lacks a trailing newline:

/**
 * Splits a raw Markdown file into its YAML block and body Markdown content.
 * Supports Windows (\r\n) line endings and files without trailing newlines.
 * @param {string} raw - The full markdown file content.
 * @returns {{yaml: string, markdown: string}}
 */
export function splitFrontmatter(raw) {
  const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
  if (!match) {
    return { yaml: "", markdown: raw };
  }
  return { yaml: match[1], markdown: match[2] };
}

/**
 * Recombines a YAML metadata block and body Markdown back into a single file string.
 * @param {string} yaml - The YAML string (without the leading/trailing --- bounds).
 * @param {string} markdown - The markdown body content.
 * @returns {string}
 */
export function joinFrontmatter(yaml, markdown) {
  const trimmedYaml = yaml.trim();
  return trimmedYaml ? `---\n${trimmedYaml}\n---\n${markdown}` : markdown;
}

2. Usage Flow in your CMS Dashboard

On Page Load:

import { splitFrontmatter } from "./helpers.js";

// 1. Fetch raw markdown file from backend API
const response = await fetch("/api/posts/42");
const data = await response.json(); // e.g. { content: "---\ntitle: Hello\n---\nBody here" }

// 2. Parse frontmatter
const { yaml, markdown } = splitFrontmatter(data.content);

// 3. Populate your CMS metadata form inputs
// (For YAML parsing, we recommend a lightweight library like 'js-yaml')
const metadata = jsyaml.load(yaml) || {}; 
document.getElementById("post-title-input").value = metadata.title || "";

// 4. Initialize Traven with only the Markdown body content
const editor = new TravenEditor({
  element: document.getElementById("editor-mount"),
  initialValue: markdown
});

On Document Save:

import { joinFrontmatter } from "./helpers.js";

// 1. Pull current body content from Traven
const bodyMarkdown = editor.getValue();

// 2. Serialize host form inputs back to a YAML string
// (Using 'js-yaml' to ensure standard YAML serialization)
const updatedYaml = jsyaml.dump({
  title: document.getElementById("post-title-input").value,
  // ...other metadata fields
});

// 3. Recombine them into the unified document
const finalFileContent = joinFrontmatter(updatedYaml, bodyMarkdown);

// 4. Send the combined file back to your server API
await fetch("/api/posts/42", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ content: finalFileContent })
});

Note: For the parsing and serialization steps above, we recommend using the MIT-licensed js-yaml library, but you can also delegate the parsing entirely to your backend API (e.g. sending yaml and markdown as separate fields in the payload).

By decoupling the structured data from the editor, you maintain clean architectural boundaries and prevent authors from breaking document parsing layouts.


3. Server-Side Framework Forms (PHP, Django, Laravel)

Traven's <traven-editor> Web Component is specifically designed to work natively inside standard HTML forms without requiring any JavaScript glue code.

Why this works: <traven-editor> is formAssociated (so the form can find it via ElementInternals) and synchronizes a hidden <textarea name="…"> as a child of itself (so FormData always serializes it via the normal textarea code path). The hidden textarea is the load-bearing piece for any backend — ElementInternals.setFormValue is the modern supplement, but the textarea is what every server-side framework actually reads.

Plain PHP $_POST Example

<form action="/save.php" method="POST">
  <label for="post-title">Title</label>
  <input type="text" id="post-title" name="title" value="Hello World">

  <label for="post-body">Body Content</label>
  <!-- The editor acts exactly like a <textarea> -->
  <traven-editor name="body" theme="light"># Initial markdown content...</traven-editor>

  <button type="submit">Publish</button>
</form>

<!-- Load the module once in the page footer -->
<script type="module" src="/dist/traven.js"></script>

When the user submits the form, your backend receives the data precisely as if it were a standard textarea:

<?php
// save.php
$title = $_POST['title'];
$markdownBody = $_POST['body']; // Automatically contains the updated Markdown

// Validate and save to database...

Idiomatic Framework Integrations

To make integration as seamless as possible, we have created zero-dependency boilerplate snippets for the most common server-side templating engines. These snippets demonstrate how to wrap the <traven-editor> Web Component into idiomatic, reusable helpers that bind perfectly to your framework's native form validation and CSRF protection.

Choose your stack: