ForgeWP

Build Editable Room Details

Define named room fields once and render each Page's saved values through useWpMeta in a custom template.

Create a room-details Page whose title, description, capacity, and size can be updated in WordPress. The layout stays in React, while each Page stores its own room values.

This builds on Page templates and editable content. Instead of composing the main details from blocks, editors fill named fields with clear labels.

What you will build

Table · 3 columns
Value
Source
Example
Room name
Native WordPress Page title
Garden Suite
Description
room_description metadata
A light-filled room overlooking the courtyard.
Capacity
room_capacity metadata
2 guests
Size
room_size metadata
36 square metres
Layout and labels
React source
Description, capacity, and size arrangement

A schema defines the field names, types, labels, and defaults. useWpMeta() reads a saved value for the current Page and uses the supplied default when the value is unset or empty.

Before you start

Use a React-adapter theme and a WordPress test site. Activate Advanced Custom Fields (ACF) on the test site for this walkthrough's generated field-group interface. The recipe uses text fields; the practice adds a URL field. ForgeWP supplies their definitions during export, so you can work from the schema rather than manually recreating the group in ACF.

The field definitions and their editing interface are separate concerns. This walkthrough uses ACF's interface; see ACF and Exported Fields for the underlying WordPress metadata integration.

1. Create the template and schema

Run these commands from the theme root:

Theme terminal: create the room Page and field schema
pnpm forgewp make:page RoomDetails
pnpm forgewp make:editable RoomDetails --fields=room_description:text,room_capacity:text,room_size:text

They create src/app/pages/RoomDetails.tsx and cms/editables/room-details.ts. Their shared room-details slug connects the schema's field group to the generated page-room-details.php template.

2. Define the fields

Replace the schema file with:

cms/editables/room-details.ts
1import { defineEditable, getEditableDefaults, text } from "@forgewp/react"
2
3export const editable = defineEditable({
4  room_description: text({
5    label: "Room description",
6    default: "A light-filled room overlooking the courtyard.",
7  }),
8  room_capacity: text({
9    label: "Room capacity",
10    default: "2 guests",
11  }),
12  room_size: text({
13    label: "Room size",
14    default: "36 square metres",
15  }),
16})
17
18export const defaults = getEditableDefaults(editable)

The keys, such as room_size, are the saved-data identifiers. Labels are what editors see. Keep keys stable across layout updates; you can improve labels without renaming the stored values.

Capacity and size are text in this example so editors can enter complete phrases. Choose numeric fields in a design that needs numeric validation or calculations.

3. Read the current Page's values

Replace the Page component with:

src/app/pages/RoomDetails.tsx
1import { useWpMeta, useWpTitle } from "@forgewp/react"
2import { defaults } from "../../../cms/editables/room-details"
3
4export function RoomDetails() {
5  const title = useWpTitle()
6  const description = useWpMeta("room_description", defaults.room_description)
7  const capacity = useWpMeta("room_capacity", defaults.room_capacity)
8  const size = useWpMeta("room_size", defaults.room_size)
9
10  return (
11    <main className="mx-auto max-w-4xl px-6 py-16">
12      <p className="text-sm font-semibold uppercase tracking-wide text-slate-500">
13        Make yourself at home
14      </p>
15      <h1 className="mt-3 text-4xl font-bold text-slate-950">{title}</h1>
16      <p className="mt-6 text-lg leading-relaxed text-slate-600">{description}</p>
17      <dl className="mt-10 grid gap-6 border-t border-slate-200 pt-8 sm:grid-cols-2">
18        <div>
19          <dt className="text-sm font-semibold text-slate-500">Capacity</dt>
20          <dd className="mt-2 text-xl text-slate-950">{capacity}</dd>
21        </div>
22        <div>
23          <dt className="text-sm font-semibold text-slate-500">Room size</dt>
24          <dd className="mt-2 text-xl text-slate-950">{size}</dd>
25        </div>
26      </dl>
27    </main>
28  )
29}

The import goes up three levels from src/app/pages/ to the project root, then enters cms/editables/. Reusing defaults keeps the schema and template aligned.

The dl, dt, and dd elements describe a list of labels and values. The template uses the Page title for its h1; the named fields supply its remaining content.

4. Export and enter room data

Theme terminal: check and export the room template
pnpm typecheck
pnpm forgewp export --validate --strict

Use the project's equivalent typecheck if its scripts differ from the starter. For a local preview route, follow Create a Custom Page Template and add RoomDetails to the existing route switch.

  1. Update the theme on the WordPress test site and confirm ACF is active.
  2. Open an existing test Page or create one named Garden Suite.
  3. Select the generated Room Details template, associated with page-room-details.php, and save. Reload the editor if needed for the template-specific field group to appear.
  4. Find the generated Room Details Content group. Depending on the editor layout, it can appear below the main canvas.
  5. Enter a description, capacity, and size. Save the Page and view its frontend.

Keep this custom template selected. It renders the named fields directly; this version's Page body is unused because the component contains no BlockArea.

5. Verify independent Page values

  • The Page title appears as the room name.
  • Each saved field appears under the expected label and survives an editor reload.
  • A second Page using the same template can have different room details.
  • An unset or empty field uses its schema-derived default.
  • Changing a layout class and updating the test theme keeps saved values connected to the same keys.
  • Narrow screens stack the details, and wider screens display the two facts beside one another.

Defaults provide an initial/fallback display. When testing them, distinguish a field that has never been saved from one that has a saved value. Update existing saved content through WordPress; changing the source default is a separate action.

Inspect the export

Table · 2 columns
File or area
What to check
page-room-details.php
Generated custom Page wrapper
Room fragment under forgewp-static/
forgewp_get_meta_value calls using the expected keys
Generated field setup in functions.php
Field labels and a Page-template location matching page-room-details.php

Common checks

Table · 2 columns
What you see
What to check
The field group is missing
Check ACF activation, the assigned Page template, matching schema slug, and installed export
A default keeps appearing
Save a value under the exact field key and refresh applicable caches
A field edit changes a different value
Check the schema key and the first argument to its useWpMeta() call
Body blocks do not appear
This template displays named fields; add a BlockArea when the design also needs a body slot

Continue to Practice: Add a Room Enquiry Link.