Skip to content

Build Your First Page

In this tutorial you will create an “About” page template, attach a custom ACF field to it, and display it using Twig.

By the end you will understand how data flows from PHP into your Twig template — the core pattern behind every page in Lumbermill.

  • An about page template selectable from the WordPress admin
  • An ACF textarea field for an intro paragraph
  • A Twig template that renders the page title and intro text

Before creating any files, it helps to understand the pattern you are about to follow.

In a standard WordPress theme, a single PHP file handles both the data and the HTML. In Lumbermill these two concerns are split across two files:

FileRole
templates/about.phpFetches data, builds a $context array, calls Timber::render()
views/templates/about.twigReceives the context and renders HTML — no PHP logic

Every key you add to $context in PHP becomes a variable you can use in Twig. That handoff is the core pattern you will use on every page.


  1. Create the PHP template

    Create templates/about.php. The Template name: comment is what WordPress shows in the page editor’s template dropdown.

    templates/about.php
    <?php
    use Timber\Timber;
    /** Template name: About */
    $context = Timber::context();
    $context['post'] = Timber::get_post();
    Timber::render('templates/about.twig', $context);

    Timber::context() loads global data (site name, menus, etc.). Timber::get_post() fetches the current WordPress page. Storing it under the key post makes it available in Twig as {{ post }}.

  2. Create the Twig template

    Create views/templates/about.twig. Every template extends layouts/base.twig — which handles the full HTML document, header, and footer — and fills in the content block.

    {# views/templates/about.twig #}
    {% extends 'layouts/base.twig' %}
    {% block content %}
    <main class="about-page">
    <div class="container">
    <h1>{{ post.title }}</h1>
    </div>
    </main>
    {% endblock %}

    {{ post.title }} comes from $context['post'] set in the previous step. You never call get_header() or get_footer()layouts/base.twig handles that.

  3. Assign the template in WordPress

    • Create a new page in wp-admin → Pages → Add New
    • In the right sidebar under Page Attributes, open the Template dropdown
    • Select About
    • Publish the page and visit its URL — your template renders
  4. Define an ACF field group

    A field group is a collection of fields that ACF renders in the WordPress admin. You define which fields it contains in one file, and where in WordPress it should appear in a separate file.

    Create the following files:

    • Directorylumbermill/
      • Directoryacf-php/
        • Directorytemplates/
          • Directoryabout/
            • content.php
            • index.php

    Define the field in acf-php/templates/about/content.php. createFieldGroup() sets up the group — everything you add to $fieldGroup will appear as an input in the editor:

    acf-php/templates/about/content.php
    <?php
    use JorenRothman\ACFBuilder\FieldGroup;
    use JorenRothman\ACFBuilder\Fields\Basic\Textarea;
    return createFieldGroup('About - Content', function (FieldGroup $fieldGroup) {
    $introText = Textarea::make('Intro Text');
    $fieldGroup->addField($introText);
    });
  5. Register the field group location

    Register where this field group should appear in acf-php/templates/about/index.php. The ->and() call sets the location rule — here it targets pages using the About template:

    acf-php/templates/about/index.php
    <?php
    use JorenRothman\ACFBuilder\FieldGroupLocations;
    $content = loadFieldGroup(__DIR__ . '/content.php');
    $location = new FieldGroupLocations;
    $location->and('page_template', '==', 'templates/about.php');
    $content->register($location, 10);

    The first argument to ->and() is the location type. Common values are page_template, post_type, page, and options_page. See the ACF guide for a full overview.

    ACF picks up the new folder automatically — acf-php/index.php autoloads every nested index.php, so there’s no wiring step.

  6. Fetch the field and pass it to context

    Open templates/about.php and fetch the ACF field. The meta key is the field group name and field name joined with underscores and lowercased: about_content_intro_text.

    templates/about.php
    <?php
    use Timber\Timber;
    /** Template name: About */
    $context = Timber::context();
    $post = Timber::get_post();
    $context['post'] = $post;
    $context['intro_text'] = $post->meta('about_content_intro_text');
    Timber::render('templates/about.twig', $context);

    $context['intro_text'] becomes {{ intro_text }} in Twig. Every key in $context maps directly to a Twig variable — this is always how data moves from PHP to your template.

  7. Render the field in Twig

    Open views/templates/about.twig and add the intro text:

    {# views/templates/about.twig #}
    {% extends 'layouts/base.twig' %}
    {% block content %}
    <main class="about-page">
    <div class="container">
    <h1>{{ post.title }}</h1>
    {% if intro_text %}
    <p class="about-page__intro">{{ intro_text }}</p>
    {% endif %}
    </div>
    </main>
    {% endblock %}

    $post->meta() returns null when the field is empty, so {% if intro_text %} evaluates to false and skips the element — no empty <p> tags in the markup.

  8. Verify in WordPress admin

    • Open the About page in wp-admin → Pages
    • You should see an Intro Text field below the content editor
    • Fill it in, update the page, and visit it — your intro text appears on the page