ForgeWP

Create a Custom Page Template

Scaffold a React page layout, preview it locally, and assign the generated PHP template to a WordPress Page.

Use a custom page template when one WordPress Page needs a distinct, developer-authored layout, such as a campaign landing page or a special About page. The component is your source; ForgeWP renders it during export and generates a PHP template that WordPress can assign to a Page.

For a complete example with an editable Gutenberg body, follow Build a Page with Editable Content.

Keep reusable helpers separate from templates

ForgeWP scans direct .tsx files under src/app/pages/ as standalone templates. A helper placed there can therefore create an extra Page template even when another template imports it. Prefer src/app/components/ for reusable implementation components.

If a helper needs to remain in the pages directory, opt it out of standalone discovery:

src/app/pages/ProjectContent.tsx
1import { definePageConfig } from "@forgewp/react/config"
2
3export const pageConfig = definePageConfig({ exportTemplate: false })
4
5export default function ProjectContent() {
6  return <section>Shared project detail layout</section>
7}

Other templates can still import and render this component. exportTemplate: false applies to standalone directory discovery; it does not remove explicit local routes, prevent route-based export, or delete Pages already saved in WordPress. Omitting the option keeps the existing discovery convention. Set it to true to declare that a reused component is intentionally also a Page template.

Export prints the source-to-WordPress filename mapping. If another root hierarchy template or page-scanned file directly imports a Page candidate through a relative value import, export adds a warning with the importing source paths and the two choices above. The warning is a review prompt, not proof that the file is a helper. Type-only imports are excluded; aliases, re-export chains, and computed imports are not inferred by this diagnostic.

A root src/app/single-project.tsx emits single-project.php. Page-directory files recognized as singles through SingleProject.tsx naming or pageConfig.postType now emit only their single-template cache, avoiding an additional Page template. A normal AboutUs.tsx emits page-about-us.php; its actual URL comes from the WordPress Page you assign it to. Single-template URLs follow WordPress permalinks. These filenames are export targets, not React routes.

On the next render/export, stale Page/single cache files are cleared and recreated from current sources. Removing a helper/template source does not remove live WordPress content.

1. Scaffold the component

From the theme root, run:

Terminal
pnpm forgewp make:page AboutUs

The React adapter creates src/app/pages/AboutUs.tsx. It uses a named component export:

src/app/pages/AboutUs.tsx
1export function AboutUs() {
2  return (
3    <main className="mx-auto max-w-5xl px-6 py-20">
4      <h1 className="text-4xl font-bold">About Us</h1>
5      <p className="mt-6 text-lg text-zinc-600">
6        We build fast WordPress experiences with ForgeWP.
7      </p>
8    </main>
9  )
10}

Keep page-template components in src/app/pages/. The exporter scans .tsx files in this folder as page templates, so place shared UI components in src/components/ instead.

2. Add a local preview route

make:page creates the page component, but does not add a path to src/app/routes.tsx. Add the component to the route switch used by the Vite preview:

src/app/routes.tsx (minimal example)
1import { Route, Switch } from "@forgewp/react"
2import { AboutUs } from "./pages/AboutUs"
3
4export default function AppRoutes() {
5  return (
6    <Switch>
7      {/* Add this route before the fallback route in your existing switch. */}
8      <Route path="/about-us" component={AboutUs} />
9    </Switch>
10  )
11}

This is a minimal example, not a replacement for your starter's route file. Add the import with the other imports and place the <Route> inside its existing <Switch>, before the fallback route. If your file has a shared withLayout helper, use component={withLayout(AboutUs)} when you want the common header and footer in the local preview.

Run pnpm dev and visit the URL Vite prints with /about-us appended, for example http://localhost:5173/about-us. The route is for local preview; it does not configure WordPress's live routing.

3. Export and assign the template

Run the normal export command:

Terminal
pnpm export

For the AboutUs filename, the compiler generates a PHP custom page template named page-about-us.php. Its template header makes it selectable from the Page editor's Template control.

ForgeWP's generated setup can also create WordPress Pages for compiled custom templates and configured local routes, then assign the matching template. Review the Pages list after activating a new theme, especially if the site already contains pages with those slugs. You can assign a generated template to an existing Page in the editor; the template file itself does not create a new React route.

Treat the generated template as the developer's ready-made page: leave it selected when the client should use that complete design. To replace it with an editor-authored composition, select ForgeWP Builder in the Page editor. The generated page-specific PHP template detects that selection and renders the Builder canvas with the saved Page blocks; otherwise, it keeps rendering the ready-made design. Add a parent shell block when the editor should start with a particular layout and child-block arrangement. Core and plugin blocks can be nested where the shell allows them, such as placing a commerce button in a sidebar slot. Insert reusable blocks through the Page editor, and use <BlockArea /> when the ready-made React design should include an editable content slot.

4. Configure the page wrapper

A page component can export pageConfig to change how the generated PHP template uses the theme layout:

src/app/pages/Campaign.tsx
1import { definePageConfig } from "@forgewp/react/config"
2
3export const pageConfig = definePageConfig({
4  layout: "blank",
5})
6
7export function Campaign() {
8  return (
9    <main>
10      <h1>Campaign</h1>
11    </main>
12  )
13}

By default, the generated template uses the theme header and footer. layout: "blank" omits both. header and footer can also be set to false or to a custom template name. These options change the PHP wrapper, not WordPress's template hierarchy.

For menu-generated routes and custom post type templates, continue to Sync Routes and Custom Post Type Templates. For how WordPress chooses the active PHP template, see The Template Hierarchy.