Choosing Between PageReference and PageTable in ProcessWire: A Decision Guide for Repeatable Content Blocks
A practical decision guide for choosing between ProcessWire's PageReference and PageTable fieldtypes when modeling repeatable content blocks. Covers constraints, a comparison table, trade-offs, and a hands-on validation procedure you can run on a staging site.
27 Jan 2026, 05:55 UTC

The Decision: How to Model Repeatable Content in ProcessWire
When building a ProcessWire site, you often need to attach repeatable content blocks to a page—think hero sections, feature grids, testimonial carousels, or FAQ accordions. Two core fieldtypes handle this: PageReference (single or multiple page links) and PageTable (inline repeatable field groups). The choice affects editorial workflow, query performance, data portability, and long-term maintenance. This guide lays out the constraints, compares the options, and shows how to validate the right pick for your project.
Constraints That Shape the Choice
- Editorial flexibility: Can content editors reorder, nest, or reuse blocks across pages?
- Performance: How many blocks per page? How large are the subfields?
- Data integrity: Do blocks need independent permissions, versions, or workflows?
- Migration ease: Will you need to restructure content later?
- Target version: ProcessWire 3.0.165+ (stable) is assumed; APIs are stable across this line.
Quick Comparison
| Option | UI Complexity | Storage Overhead | Query Flexibility | Typical Use Case |
|---|---|---|---|---|
| PageReference | Low (single/multi-select dropdown or ASMSelect) | Minimal (stores page IDs only) | High (native $pages->find(), selectors, permissions) | Simple links, related articles, category tags, reusable blocks |
| PageTable | Medium (inline repeater with drag-sort, collapsible rows) | Higher (serialized field data per row) | Moderate (custom selectors on serialized data; no native page API) | Complex repeatable blocks with varied fields (hero, feature card, FAQ item) |
Trade-offs in Depth
PageReference: Leverage the Page Tree
Each block lives as a full page (usually under a hidden /blocks/ parent). You get:
- Fast lookups:
$page->blocks->find("template=hero")uses indexedpages_idjoins. - Permissions & workflow: Editors can be restricted to edit only certain block templates.
- Reusability: The same block page can be referenced from multiple parents.
- Versioning: ProcessWire's native page history tracks changes per block.
Downsides: every block is a page, so the tree grows. Editors must navigate away from the parent page to edit block content (unless you use InputfieldPageTableExtended or similar modules). Creating a new block requires a separate "Add New" step.
PageTable: Contained, Inline Editing
Blocks are stored as serialized rows inside the parent page's field data. You get:
- Inline UX: Editors add, reorder, and edit blocks without leaving the page.
- Containment: No orphan blocks; deleting the parent removes its PageTable rows.
- Schema flexibility: Each row can have a different field set (via
PageTablefieldtype's "fields per row" config).
Downsides: serialized blobs can bloat the fields table. Queries like "find all pages with a hero block where headline contains 'sale'" require FieldtypePageTable::getMatchQuery() or raw SQL. No independent permissions—block editing follows the parent page's edit permission. Migration out of PageTable means writing a script to explode rows into pages.
Concrete Validation: Test Both on a Staging Site
Before committing, spin up a staging copy of your site (or a fresh ProcessWire 3.0.165+ install) and run this verification. You need superuser access to create fields and templates.
Step 1: Create Test Templates and Fields
# In Admin > Setup > Templates:
# 1. Create template "test_block_hero" (no file) with fields: headline (text), cta_text (text), cta_url (url).
# 2. Create template "test_block_faq" with fields: question (textarea), answer (textarea).
# 3. Create template "test_parent" with two fields:
# - field_pr (PageReference, multiple, inputfield=AsmSelect, selectable templates=test_block_hero|test_block_faq)
# - field_pt (PageTable, fields=headline,cta_text,cta_url,question,answer; row templates=hero,faq)
Where to run: ProcessWire admin (Setup → Fields → Add New, then Setup → Templates). Permissions: superuser or field/template create rights. Risk: none on staging; do not run on production without backup.
Step 2: Add Sample Data
# Edit a page using template "test_parent".
# In field_pr: create 3 new pages (2x hero, 1x faq) via the ASMSelect "Add New" link.
# In field_pt: add 3 rows (hero, hero, faq) inline, fill each field.
# Save.
Step 3: Inspect Data Structures
<?php
// In a template file or Tracy Debugger console:
$page = $pages->get('/test-parent/'); // adjust path
// PageReference returns a PageArray
$pr = $page->field_pr;
echo "PageReference count: " . $pr->count() . "\n";
foreach ($pr as $block) {
echo " - " . $block->template->name . ": " . $block->headline . "\n";
}
// PageTable returns a PageArray of PageTableRow objects
$pt = $page->field_pt;
echo "PageTable count: " . $pt->count() . "\n";
foreach ($pt as $row) {
echo " - Row type: " . $row->pageTableRowType . "\n";
// Access fields dynamically
foreach ($row->getFields() as $f) {
if ($row->$f) echo " $f: " . $row->$f . "\n";
}
}
?>
Expected check: PageReference gives you full Page objects with all page methods. PageTable gives PageTableRow objects—field access works but they are not pages (no $row->id, no $row->template in the page sense). Confirm the output matches your mental model.
Step 4: Compare Query Performance
- Enable Debug mode (
$config->debug = true;insite/config.php). - Load the test parent page in the front-end.
- Open the Debug toolbar (bottom-right) → "Queries" tab.
- Note query count and time for each field access. PageReference typically shows 1–2 indexed joins. PageTable shows a single
SELECT data FROM field_field_pt WHERE pages_id=…plus unserialization overhead.
Limitation: This micro-benchmark doesn't reflect production scale. For 50+ rows per page, PageTable blob size can exceed MySQL's max_allowed_packet or slow unserialization. Monitor LENGTH(data) in field_field_pt if you anticipate large rows.
Step 5: Verify Editorial Constraints
In each field's configuration (Setup → Fields → field_pr / field_pt → Input tab), set:
- PageReference: "Maximum pages" = 5. Test that the ASMSelect disables "Add New" after 5 selections.
- PageTable: "Maximum rows" = 5. Test that the "Add" button disappears after 5 rows.
Confirm enforcement in the admin UI. This validates that your chosen fieldtype respects the content model's limits.
Migration Reality Check
If you later need to switch fieldtypes, there is no automated conversion. A typical migration script outline:
<?php
// PageReference → PageTable
$parents = $pages->find("template=test_parent");
foreach ($parents as $parent) {
$parent->of(false);
foreach ($parent->field_pr as $block) {
$row = $parent->field_pt->makeBlankRow();
$row->pageTableRowType = $block->template->name; // 'hero' or 'faq'
// Map fields manually
if ($block->template->name === 'hero') {
$row->headline = $block->headline;
$row->cta_text = $block->cta_text;
$row->cta_url = $block->cta_url;
} else {
$row->question = $block->question;
$row->answer = $block->answer;
}
$parent->field_pt->add($row);
}
$parent->save();
// Optionally delete old block pages
}
?>
Run context: CLI script via php index.php --script=migrate.php or Tracy Debugger. Permissions: superuser. Risk: data loss if mapping is wrong—always test on a database copy first. Rollback: restore the database snapshot.
Decision Checklist
- Do blocks need to be reused across pages? → PageReference.
- Do editors need to stay on one page while building complex layouts? → PageTable.
- Will you query across blocks (e.g., "all FAQ questions containing 'shipping'")? → PageReference.
- Is the block structure simple (1–3 fields) and unlikely to change? → PageReference is lighter.
- Do you need per-block permissions or workflow? → PageReference.
- Are you building a page builder with 10+ row types and 50+ rows per page? → PageTable (but watch blob size).
Final Recommendation
For most content-driven sites, PageReference wins on query flexibility, permissions, and long-term portability. Use PageTable when the editorial experience demands true inline editing of heterogeneous blocks and the blocks are tightly bound to a single parent page. Run the validation steps above on your actual field configurations before locking in the choice.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.