ForgeWP

Compiler Contracts and Translation Rules

A complete reference of ForgeWP compilation rules: how React data hooks, JSX primitives, editable schemas, Gutenberg blocks, and forms map to native WordPress PHP, editor scripts, or client runtime islands.

The power of ForgeWP lies in its clear compilation contracts. Rather than trying to emulate a full Node.js runtime inside PHP, ForgeWP translates recognized React hooks, primitives, and component patterns into the appropriate WordPress output.

Every line of code you write in a ForgeWP theme is compiled into one of three execution environments:

  1. Compile-Time Server (PHP / Static HTML): Evaluated during the export pass and written as native PHP templates with WordPress template tags. Zero client JavaScript is sent to the visitor.
  2. Editor Environment (Gutenberg in /wp-admin): Compiled into @wordpress/block-editor components, attribute schemas (block.json), and sidebar controls for content creators.
  3. Client Runtime (Hydrated Browser Islands): Bundled into isolated vanilla JavaScript chunks that execute in the visitor's browser only when triggered.
Flowchart
Rendering diagram...

The Master Translation Matrix

Table · 4 columns
React Construct
Primary Execution Host
Emitted WordPress Output
Client JS Shipped?
useWpTitle()
Server (PHP)
get_the_title() with entity decoding
No (0 KB)
useWpContent()
Server (PHP)
the_content()
No (0 KB)
useWpExcerpt()
Server (PHP)
the_excerpt()
No (0 KB)
useWpPermalink()
Server (PHP)
the_permalink()
No (0 KB)
useWpDate()
Server (PHP)
get_the_date()
No (0 KB)
useWpAuthor()
Server (PHP)
get_the_author()
No (0 KB)
useWpFeaturedImage()
Server (PHP)
get_the_post_thumbnail_url()
No (0 KB)
useWpCustomField()
Server (PHP)
get_post_meta()
No (0 KB)
useWpMeta()
Server (PHP)
forgewp_get_meta_value()
No (0 KB)
useWpOption()
Server (PHP)
get_option()
No (0 KB)
useWpThemeMod()
Server (PHP)
get_theme_mod()
No (0 KB)
useWpI18n()
Server (PHP)
__('text', 'domain')
No (0 KB)
useWpLanguage() (read-only)
Server (PHP)
Polylang language URLs
No (0 KB)
<BlockArea />
Server (PHP)
the_content() in template slot
No (0 KB)
<WpMenu />
Server (PHP)
wp_get_nav_menu_items()
No (0 KB)
<WpShortcode />
Server (PHP)
do_shortcode()
No (0 KB)
<WpQueryLoop> (static)
Server (PHP)
new WP_Query(...) loop
No (0 KB)
defineBlock()
Dual Host
block.json, render.php, editor JS
Only in Admin
<WpEditable />
Dual Host
Editor: RichText / Frontend: text
Only in Admin
useWpQuery()
Client Island
Hydrated island fetching via REST
Yes (isolated chunk)
useWpForm()
Client Island
Native form submitter & validation
Yes (isolated chunk)
onClick, useState
Client Island
Extracted island via <Hydrate>
Yes (isolated chunk)

WordPress Data Hooks to PHP Mappings

During the compile-time SSR pass, ForgeWP data hooks emit intermediate tokens that the markup processor converts into PHP expressions:

1. Core Post Data

src/components/ArticleHeader.tsx (React Source)
1import { useWpTitle, useWpDate, useWpAuthor } from "@forgewp/react"
2
3export function ArticleHeader() {
4  return (
5    <header>
6      <h1>{useWpTitle()}</h1>
7      <p>Published on {useWpDate()} by {useWpAuthor()}</p>
8    </header>
9  )
10}
Generated Output in Theme Template
1<header>
2  <h1><?php echo html_entity_decode( get_the_title(), ENT_QUOTES | ENT_HTML5, 'UTF-8' ); ?></h1>
3  <p>Published on <?php echo esc_html( get_the_date() ); ?> by <?php echo esc_html( get_the_author() ); ?></p>
4</header>

2. Custom Meta Fields and Editable Schemas

The useWpCustomField() hook maps directly to WordPress post meta:

src/components/ProjectBadge.tsx (React Source)
1import { useWpCustomField } from "@forgewp/react"
2
3export function ProjectBadge() {
4  const clientName = useWpCustomField("client_name", "Private Client")
5  return <span className="badge">{clientName}</span>
6}
Generated Output in Theme Template
1<span class="badge"><?php echo esc_html( get_post_meta( get_the_ID(), 'client_name', true ) ); ?></span>

For schema-managed fields using useWpMeta(), the compiler generates calls to the forgewp_get_meta_value() helper:

Generated Output for useWpMeta('hero_title', 'Welcome')
1<?php echo forgewp_get_meta_value( 'hero_title', 'Welcome', false, null ); ?>

This helper:

  • Checks for post meta via get_post_meta() or ACF's get_field().
  • Falls back to the default value declared in your schema if the meta is empty.
  • Automatically handles front-page language translations when viewing Polylang sites.

JSX Primitives to WordPress Output

ForgeWP provides specialized JSX components that act as compile-time directives:

1. <BlockArea /> (Designated Post Content Slot)

When authoring page templates or single post layouts, <BlockArea /> designates where WordPress Block Editor content renders:

src/app/pages/SinglePost.tsx
1import { BlockArea, useWpTitle } from "@forgewp/react"
2
3export function SinglePost() {
4  return (
5    <article className="prose mx-auto py-12">
6      <h1>{useWpTitle()}</h1>
7      <BlockArea name="post-content" />
8    </article>
9  )
10}
Generated page.php / single.php Output
1<article class="prose mx-auto py-12">
2  <h1><?php echo html_entity_decode( get_the_title(), ENT_QUOTES | ENT_HTML5, 'UTF-8' ); ?></h1>
3  <?php the_content(); ?>
4</article>

2. <WpMenu /> (Navigation Walker)

<WpMenu /> emits a server-side navigation loop that resolves the correct menu based on the active language:

src/components/Navbar.tsx
1import { WpMenu } from "@forgewp/react"
2
3export function Navbar() {
4  return (
5    <header>
6      <WpMenu location="primary" className="flex gap-4" linkClassName="text-sm font-medium" />
7    </header>
8  )
9}
Generated PHP Output
1<header>
2  <?php
3  $lang = function_exists('pll_current_language') ? pll_current_language() : '';
4  $menu_id = function_exists('forgewp_get_menu_id_for_lang') ? forgewp_get_menu_id_for_lang('primary', $lang) : null;
5  $menu_items = $menu_id ? wp_get_nav_menu_items($menu_id) : array();
6  if (!empty($menu_items)) {
7      echo '<nav class="flex gap-4">';
8      foreach ($menu_items as $item) {
9          echo '<a href="' . esc_url($item->url) . '" class="text-sm font-medium">' . esc_html($item->title) . '</a>';
10      }
11      echo '</nav>';
12  }
13  ?>
14</header>

3. <WpShortcode /> (WordPress Shortcode Evaluator)

<WpShortcode /> displays an illustrative placeholder in local Vite preview. During export, the compiler converts it to a standard do_shortcode() call:

React Source
1<WpShortcode code="[contact-form-7 id='42' title='Inquiry']" />
Generated PHP Output
1<?php echo do_shortcode('[contact-form-7 id=\'42\' title=\'Inquiry\']'); ?>

Editable Content Schemas and Rich Text Rules

When you declare field schemas with defineEditable(), the compiler enforces strict typing rules for how content is stored and escaped:

cms/editables/front-page.ts
1import { defineEditable, text, richText, image } from "@forgewp/react"
2
3export const editable = defineEditable({
4  hero_title: text({ default: "Luxury Mountain Suites" }),
5  hero_description: richText({ default: "<p>Experience handcrafted chalet living.</p>" }),
6  hero_image: image({ default: "/images/hero.jpg" }),
7})

Why the Schema is Authoritative for wpautop()

In standard WordPress, rich text fields need wpautop() to convert double line breaks into <p> paragraphs. Plain text fields must never be run through wpautop(), otherwise WordPress will wrap plain strings in <p> tags, breaking layout styling and double-nesting tags.

The ForgeWP compiler tracks the exact set of fields declared as richText(). When compiling useWpMeta("hero_description"), it marks the third parameter of forgewp_get_meta_value() as true:

Generated Output for richText
1<?php echo forgewp_get_meta_value( 'hero_description', '...', true, null ); ?>

When compiling hero_title (declared as text()), the parameter is false, ensuring plain text is escaped safely without unintended paragraph wrapping.


Dual-Host Block Transpilation

When you define a Gutenberg block with defineBlock(), the compiler processes your component for two distinct target environments:

Flowchart
Rendering diagram...

Host 1: The Gutenberg Editor (assets/forgewp-editor.js)

The editor transpiler (editor-transpiler.js) transforms your JSX into a WordPress Block Editor component:

  • Injects useBlockProps().
  • Converts <WpEditable name="title"> into @wordpress/block-editor's <RichText value={attributes.title} onChange={(val) => setAttributes({ title: val })} />.
  • Connects custom attributes to the block sidebar inspector.
  • Emits block.json containing the attribute schemas and block metadata.

Host 2: The Visitor Frontend (render.php)

The PHP transpiler (php-transpiler.js) converts that same JSX into a fast, server-side PHP renderer:

  • Strips all <WpEditable> wrappers.
  • Replaces attribute values with $attributes['title'] ?? 'default'.
  • Transpiles JSX conditional ternaries ({condition ? <ComponentA /> : <ComponentB />}) into PHP if/else statements.
  • Transpiles JSX array mappings ({items.map(...)}) into PHP foreach loops.

Visitors receive 100% static, server-rendered HTML with zero client JavaScript overhead for the block.


Selective Hydration Boundaries (is-interactive.js)

ForgeWP's AST analyzer inspects components to identify static markup and interactive islands, helping keep browser JavaScript focused on the parts that need it.

The SERVER_SAFE_HOOKS Allowlist

Hooks on the SERVER_SAFE_HOOKS allowlist do not by themselves require hydration. A component that otherwise stays static can need no component-specific browser JavaScript. Interactive children, imports, surrounding islands, and shared scripts still affect the full page payload:

Server-safe hooks catalog
useWpTitle
useWpContent
useWpExcerpt
useWpPermalink
useWpDate
useWpModifiedDate
useWpAuthor
useWpFeaturedImage
useWpCustomField
useWpMeta
useWpOption
useWpThemeMod
useWpThemeUri
useWpI18n
useWpLanguage (without switchLanguage handlers)
useWpMenu
useReducedMotion
useMemo
useCallback
useId

What Triggers Client Hydration?

A component is automatically flagged as interactive when it uses:

  • React state or effect hooks: useState, useEffect, useReducer, useRef.
  • Asynchronous data fetching hooks: useWpQuery, useWpForm.
  • DOM event handlers: onClick, onChange, onSubmit, onMouseEnter.
  • Client animation packages: framer-motion, motion/react, gsap.
  • Explicit hydration boundaries: <Hydrate>.

Debugging Compiler Transformations

When an exported page or block does not render as expected, follow this systematic debugging procedure:

1. Inspect Generated PHP Templates

Open the compiled files inside .forgewp/out/<theme-slug>/:

  • Inspect functions.php to verify enqueues, registered post types, and REST endpoints.
  • Inspect page.php or front-page.php to verify that your markup tokens were replaced by PHP tags.
  • Inspect blocks/<slug>/render.php to verify how block attributes are read.

2. Run Export in Strict Mode

Run export with --validate --strict to expose unmapped editable fields or missing hydration assets:

Terminal
pnpm forgewp export --validate --strict

3. Verify PHP Syntax Locally

If you have PHP installed on your development machine, verify syntax across all generated files:

Terminal
php -l .forgewp/out/alpine-resort/functions.php
php -l .forgewp/out/alpine-resort/front-page.php

Next Steps

Now that you understand the compiler contracts, translation rules, and dual-host block architecture, proceed to AST Transformations and Transpiler Internals to explore how Babel and TypeScript syntax trees, static node evaluation, and range-splicing power ForgeWP's compilation engine.