← Back to Blog

Developer Quickstart: Implementing LiveCMS in 5 Minutes

To integrate LiveCMS into a WordPress theme, developers must include the core CSS and JS assets and then wrap editable template regions with specific HTML comments (e.g., <!-- lcms:text field_name -->). This markup-driven approach eliminates the need for Advanced Custom Fields (ACF) and allows for real-time frontend editing without touching the WordPress dashboard. It is designed to replace the heavy "edit-save-refresh" loop that currently slows down 90% of custom theme development.

For the agency-facing overview — the gap in block-centric frontend editors, what clients should and should not touch, and when to choose LiveCMS versus Gutenberg or ACF — start with Inline Content Editor for Developer-Built WordPress Themes.

Code

Overview

Template (PHP)                    LiveCMS Plugin (JS)
─────────────────                 ───────────────────
<!-- lcms:type name -->    →      Scanner finds comments
<element>content</element>  →      Injects data-lcms-* attributes
<!-- /lcms -->              
  1. Developer adds HTML comments to mark editable areas
  2. The JS scanner runs on page load, finds comments, injects data-lcms-* attributes on the next element
  3. When an editor clicks, the appropriate modal opens
  4. On save, the REST API writes to a JSON file in the theme's lcms-data/ directory
  5. On next page load, PHP reads the JSON and renders the content

Theme Requirements: wp_head() and wp_footer()

LiveCMS loads through two standard WordPress hook points. If a template skips them, the editor never loads and the admin bar (with the ✏️ LiveCMS toggle) never renders — even for logged-in admins.

Templates using get_header() / get_footer() already fire both — nothing to do. Self-contained templates (which skip get_header()/get_footer() to avoid doubling site chrome) must fire the hooks themselves:

<!doctype html>
<html <?php language_attributes(); ?>>
<head>
  <meta charset="<?php bloginfo('charset'); ?>">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- your design CSS/JS -->
  <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>

  <!-- template markup with lcms comment markers -->

  <?php wp_footer(); ?>
</body>
</html>

The toggle additionally requires: logged in, a role with the edit_posts capability, and Users → Profile → "Show Toolbar when viewing site" enabled. Anonymous visitors never see the admin bar — that is normal WordPress behavior.

Agent Skill (AI coding assistants)

If you build templates with an AI coding agent (Claude Code, Codex CLI, Kiro, Gemini CLI, or any Agent-Skills-compatible tool), install the bundled skill so the agent marks up templates correctly — wrapping all editable areas with the right <!-- lcms:type field --> comments automatically.

Install (one-time):

# Kiro IDE
mkdir -p .kiro/skills
cp -r wp-content/plugins/live-cms/skills/live-cms .kiro/skills/

# Codex CLI / OpenCode / Cursor (cross-tool convention)
mkdir -p ~/.agents/skills
cp -r wp-content/plugins/live-cms/skills/live-cms ~/.agents/skills/

# Claude Code
mkdir -p ~/.claude/skills
cp -r wp-content/plugins/live-cms/skills/live-cms ~/.claude/skills/

Usage:

Once installed, ask in natural language:

Or use the slash command where supported: /live-cms

What the skill does:

The agent will automatically:

  1. Read your template and identify all editable content areas
  2. Wrap each area with the correct <!-- lcms:type field_name --> comment markers
  3. Generate a data-prep header block ($d = lcms_data() + per-field variables)
  4. Create the matching lcms-data/defaults/{template}.json file with meaningful default values

It knows the field types (text, textarea, image, link, repeater, post), naming rules (snake_case, section-prefixed), and what NOT to wrap (nav, icons, backgrounds, footer).

See skills/live-cms/install/README.md for project-scope install and per-agent notes.

Comment Markup Reference

Every editable area is wrapped with an opening and closing comment:

<!-- lcms:{type} {field_name} -->
<element>content here</element>
<!-- /lcms -->

Field Types

text — Single-line text

<!-- lcms:text hero_heading -->
<h1>Your Heading Here</h1>
<!-- /lcms -->

Editor: Single-line input JSON: "hero_heading": "Your Heading Here"

textarea — Multi-line text

<!-- lcms:textarea intro_body -->
<p>A longer paragraph of text that editors can change.</p>
<!-- /lcms -->

Editor: Multi-line textarea styled to match the element's font JSON: "intro_body": "A longer paragraph..."

image — Image from Media Library

<!-- lcms:image hero_image -->
<div class="image-wrapper">
  <img src="<?php echo esc_url($hero_image); ?>" alt="" />
</div>
<!-- /lcms -->

Editor: Two tabs — Media Library browser + drag-and-drop upload JSON: "hero_image": "/wp-content/uploads/2026/04/photo.jpg" (relative path)

link — Button or link (text + URL + target)

<!-- lcms:link cta_link -->
<a href="<?php echo esc_url($link_url); ?>">
  <?php echo esc_html($link_text); ?>
</a>
<!-- /lcms -->

Editor: Text input + URL input + "Open in new tab" checkbox JSON: "cta_link": { "text": "Learn More", "url": "/about", "target": "" } Supports: Full URLs, relative paths, anchors (#), mailto:, tel:

repeater — List of items (add/remove/reorder)

<!-- lcms:repeater team_members -->
<div class="grid">
  <?php foreach ($members as $m) : ?>
    <div class="card">...</div>
  <?php endforeach; ?>
</div>
<!-- /lcms -->

Editor: Modal with item list. Each item has: title, textarea, url, thumb (text label), image (Media Library) JSON:

"team_members": [
  { "title": "Jane", "thumb": "CEO", "textarea": "Bio text", "image": "/uploads/jane.jpg", "url": "" },
  { "title": "John", "thumb": "CTO", "textarea": "Bio text", "image": "/uploads/john.jpg", "url": "" }
]

post — Article/post picker

<!-- lcms:post kb_cards.0 -->
<div class="card">...</div>
<!-- /lcms -->

Editor: Search box + list of published posts. Click to select. JSON: "kb_cards": [{ "post_id": 87 }, { "post_id": 0 }] (0 = auto-fill from latest)

wpblock — Gutenberg block (stored in DB, not JSON)

Generated automatically by the template loop — not manually written.

<!-- lcms:wpblock {index} {editor_type} {block_name} -->
<p>Paragraph content from Gutenberg</p>
<!-- /lcms -->

Editor: textarea for paragraphs, text input for headings, image editor for images Saves to: wp_posts.post_content (WordPress database)

wpfield — WordPress post field (title, excerpt)

<!-- lcms:wpfield post_title text -->
<h1><?php echo get_the_title(); ?></h1>
<!-- /lcms -->

Editor: text input Saves to: wp_posts table

wpauthor — Author picker

<!-- lcms:wpauthor post_author -->
<span><?php echo get_the_author(); ?></span>
<!-- /lcms -->

Editor: Dropdown of WordPress users Saves to: wp_posts.post_author

wpdate — Date picker

<!-- lcms:wpdate post_date -->
<span><?php echo get_the_date('j F Y'); ?></span>
<!-- /lcms -->

Editor: Native HTML date picker Saves to: wp_posts.post_date

PHP Helper Functions

Provided by includes/json-store.php:

// Get all content for current post/page
$data = lcms_get_content();
$data = lcms_get_content($post_id);

// Get a single field with default fallback
$heading = lcms_get('hero_heading', 'Default Value');
$heading = lcms_get('hero_heading', 'Default', $post_id);

// Get a repeater array
$items = lcms_get_repeater('team_members');

// Resolve image path to full URL
$url = lcms_image_url('/wp-content/uploads/photo.jpg');
// Returns: http://yoursite.local/wp-content/uploads/photo.jpg
// Empty string returns transparent SVG placeholder

JSON Data Storage

themes/{theme}/lcms-data/
├── content/                              # Per-page/post content
│   ├── page-about-us-259.json           # {type}-{slug}-{id}.json
│   ├── page-live-cms-demo-197.json
│   └── post-my-article-123.json
└── defaults/                             # Fallback values per template
    ├── page-about-us.json               # Matches template filename
    └── page-lcms-test.json

REST API Endpoints

All require authentication (X-WP-Nonce header, edit_posts capability).

Endpoint Method Body Purpose
/live-cms/v1/update-field POST { field, post_id, value } Update text/textarea/link/repeater
/live-cms/v1/upload-image POST FormData: file, field, post_id Upload image + save path
/live-cms/v1/update-post POST { post_id, field, value } Update WP post field (title)
/live-cms/v1/update-block POST { post_id, block_index, value, block_type } Update Gutenberg block
/live-cms/v1/update-block-image POST FormData: file, post_id, block_index Replace block image
/live-cms/v1/update-thumbnail POST FormData: file, post_id Update featured image
/live-cms/v1/update-author POST { post_id, author_id } Change post author
/live-cms/v1/update-date POST { post_id, date } Change publish date
/live-cms/v1/content/{post_id} GET — Read all JSON content
/live-cms/v1/posts/search?q=term GET — Search posts (for post picker)
/live-cms/v1/authors GET — List authors (for author picker)

Template Examples

Basic page template

<?php
/**
 * Template Name: My Page
 */
get_header();
$page_id = get_the_ID();
$data = lcms_get_content($page_id);

$heading = isset($data['heading']) ? $data['heading'] : 'Default';
$body = isset($data['body']) ? $data['body'] : '';
$hero_img = lcms_image_url(isset($data['hero_image']) ? $data['hero_image'] : '');
?>

<!-- lcms:text heading -->
<h1><?php echo esc_html($heading); ?></h1>
<!-- /lcms -->

<!-- lcms:textarea body -->
<p><?php echo wp_kses_post($body); ?></p>
<!-- /lcms -->

<!-- lcms:image hero_image -->
<div><img src="<?php echo esc_url($hero_img); ?>" alt="" /></div>
<!-- /lcms -->

<!-- lcms:link cta -->
<?php $cta = isset($data['cta']) && is_array($data['cta']) ? $data['cta'] : []; ?>
<a href="<?php echo esc_url($cta['url'] ?? '#'); ?>">
  <?php echo esc_html($cta['text'] ?? 'Click Here'); ?>
</a>
<!-- /lcms -->

<!-- lcms:repeater features -->
<div>
  <?php foreach (lcms_get('features', []) as $f) : ?>
    <div>
      <h3><?php echo esc_html($f['title'] ?? ''); ?></h3>
      <p><?php echo wp_kses_post($f['textarea'] ?? ''); ?></p>
    </div>
  <?php endforeach; ?>
</div>
<!-- /lcms -->

<?php get_footer(); ?>

Single post template (with wpblock)

<?php
$blocks = parse_blocks(get_the_content(null, false));
$idx = 0;
foreach ($blocks as $block) {
    if (empty($block['blockName'])) continue;
    $rendered = render_block($block);
    $type = 'textarea';
    if ($block['blockName'] === 'core/heading') $type = 'text';
    if ($block['blockName'] === 'core/image') $type = 'wpimage';

    $editable = ['core/paragraph', 'core/heading', 'core/image'];
    if (in_array($block['blockName'], $editable, true)) {
        echo '<!-- lcms:wpblock ' . $idx . ' ' . $type . ' ' . esc_attr($block['blockName']) . ' -->';
        echo $rendered;
        echo '<!-- /lcms -->';
    } else {
        echo $rendered;
    }
    $idx++;
}
?>

AI / Figma MCP Integration

When using AI tools or Figma MCP to generate page templates:

  1. The AI/MCP generates the HTML/PHP with Tailwind styling as usual
  2. The developer (or AI with steering rules) adds <!-- lcms:type name --> comments around editable content
  3. A matching defaults JSON file is created at lcms-data/defaults/{template-name}.json

Steering rule (.kiro/steering/lcms-page-generation.md) instructs the AI to:

What to wrap:

What NOT to wrap:


Plugin Files

plugins/live-cms/
├── live-cms.php              # Plugin bootstrap, REST API, admin bar
├── uninstall.php             # Cleanup on plugin deletion
├── readme.txt                # WordPress.org listing metadata
├── LICENSE                   # GPL-2.0-or-later
├── README.md                 # This file (developer docs)
├── includes/
│   ├── json-store.php        # lcms_get(), lcms_set(), lcms_image_url(), lcms_data()
│   ├── licensing.php         # Permission helpers (lcms_can_edit, lcms_can_upload)
│   └── analytics.php         # Opt-in activation stats (GA4 Measurement Protocol)
├── assets/
│   ├── live-cms.prod.js      # Editor (minified) — comment scanner, all modals
│   ├── live-cms.css          # Editor overlay styles
│   ├── lcms-logo.jpeg        # Plugin branding
│   └── marked.min.js         # Markdown renderer for in-admin docs
└── skills/
    └── live-cms/             # AI agent skill (copy to your agent's skills dir)
        ├── SKILL.md
        ├── install/README.md
        └── references/       # Detailed markup, helpers, and generation rules