26 June 2026 by Ryan Cramer
I originally set up a recurring task in AgentTools that asks AI agents to pick a ProcessWire subject or feature that's not well covered elsewhere, and to write a blog post about it. This was intended as a test for AgentTools scheduled and recurring tasks, and also as a good way of getting ideas. But as I read through them, I really connected with a few. This is one of them, written by GLM 5.1, which I think did a particularly good job explaining the $page->meta() method. I hope you enjoy! —Ryan
If you've been working with ProcessWire for a while like I have, you probably think of page data in terms of fields. Want to store something on a page? Create a field, add it to the template, and populate it in the admin.
That's the usual ProcessWire way, and it's a great way — but it's not the only way.
In ProcessWire, every page with an ID has a built-in key-value store that requires zero field configuration, zero template changes, and zero admin clutter. It's called meta(), and it might just be the most underused method in the entire API.
What is $page->meta()?
The meta() method provides a simple, persistent, per-page key-value store. Think of it as a drawer attached to every page where you can stash arbitrary data — scalar values and arrays — without needing to define a field for it. The data lives in its own database table, completely independent of the page's regular field values.
Here's the beauty: you don't need to touch Setup > Fields or Setup > Templates. No fieldgroup edits. No template changes. Just call the method and go. Or, as my AI friend GPT 5.5 over at OpenAI put it: it’s the storage equivalent of finding a secret drawer in furniture you already owned.
Getting and Setting Values
Setting a meta value is a one-liner, and it's saved to the database immediately — no separate save() call required:
// Set a meta value (saved immediately!)
$page->meta('view_count', 42);
$page->meta('last_synced', '2026-06-03');
// Arrays work too
$page->meta('colors', ['red', 'green', 'blue']);
// Alternative syntax (using the WireDataDB object)
$page->meta()->set('priority', 'high');
Getting values is just as easy. Values are loaded lazily from the database on first access, so there's no performance penalty until you actually need the data (though a single DB query is hardly a performance penalty):
// Get a meta value
$colors = $page->meta('colors');
$count = $page->meta('view_count');
// Alternative syntax
$priority = $page->meta()->get('priority');
// Get ALL meta values as an associative array
$all = $page->meta()->getArray();
// Returns: ['colors' => ['red','green','blue'], 'view_count' => 42, ...]
Removing Values
You can remove individual keys or wipe the whole store:
// Remove a single key
$page->meta()->remove('colors');
// Remove everything for this page
$page->meta()->removeAll();
// Check how many meta entries exist
$n = count($page->meta());
Real-World Use Cases
1. Caching Computed Data
Suppose your blog post template computes a "reading time" value based on the body field length. Rather than calculating it on every page load, or creating a dedicated field that you have to remember to populate, compute it once and store it in meta:
// In your template file or a module
$readingTime = $page->meta('reading_time');
if(!$readingTime) {
$wordCount = str_word_count(strip_tags($page->body));
$readingTime = ceil($wordCount / 200); // ~200 wpm
$page->meta('reading_time', $readingTime);
}
echo "$readingTime min read";
2. Storing External API State
When integrating with external services, you often need to track synchronization state — the last time data was pulled, a remote ID, or an API response timestamp. Meta keeps this bookkeeping data off your content fields:
// Track when this page was last synced to an external system
$page->meta('remote_id', $apiResponse['id']);
$page->meta('last_sync', time());
// Later, check if we need to re-sync
$lastSync = $page->meta('last_sync');
if(!$lastSync || (time() - $lastSync) > 86400) {
// Re-sync the page
syncToExternalService($page);
}
3. Feature Flags for Pages
Need to toggle a feature on or off per page, but don't want to clutter your template with yet another checkbox field? Meta is perfect for feature flags:
// Enable an experimental layout for this page
$page->meta('layout_variant', 'compact');
// In your template file
$variant = $page->meta('layout_variant');
if($variant === 'compact') {
// Render compact layout
} else {
// Render standard layout
}
4. Computed Caching with getCache()
Meta also supports time-based caching via getCache(). This is perfect for values that are expensive to compute but have a reasonable shelf life:
$relatedPostIds = $page->meta()->getCache('related_posts', 3600,
function() use($page, $pages) {
$s = "template=blog-post, categories=$page->categories, id!=$page";
return $pages->findIDs($s);
}
);
The getCache() method takes three arguments: a key name, a maximum age in seconds, and a callable that produces the value when the cache is empty or expired. It stores the result in meta with a timestamp, and automatically refreshes when the time is up. No cron jobs. No cache management. Just set-it-and-forget-it caching.
Why Not Just Use Fields?
Good question. Fields are still the right choice for content that editors need to see and manage in the admin. But meta shines when the data is:
Computed or derived — values calculated from other data, not entered by humans
Internal bookkeeping — state that would confuse editors if it appeared on the page edit screen
Temporary or experimental — flags or counters you're trying out before committing to a real field
External — IDs, timestamps, or tokens from third-party integrations
Fields also come with overhead: each one adds to your template's fieldgroup, increases the page editor UI, and requires field configuration and its own field table/schema. Meta values share a single lightweight table, and the API is so simple you can start using it in minutes.
Things to Keep in Mind
Values must be scalar or array — you can't store objects. Strings, integers, floats, booleans, and arrays of those types are all fair game.
Meta values are not versioned — they're not subject to page version history (like with PagesVersions). This is by design; meta is for ephemeral and computed data.
Meta values are not searchable by selectors — you can't do $pages->find('meta_key=value'). If you need to query by a value, it belongs in a proper field. Meta is a drawer, not a filing cabinet.
Access control follows the page — Meta has no separate permission layer or admin UI; code that can access the Page object can read or write its meta values.
Putting It All Together
Here's a simplified but more complete example showing meta in action within a module that tracks and displays page view counts:
// In a module's init() method
$this->addHookAfter('Page::render', $this, 'trackPageView');
public function trackPageView(HookEvent $e) {
$page = $e->object;
// Only track pages with specific templates
if(!$page->template->hasField('body')) return;
$views = (int) $page->meta('views');
$page->meta('views', $views + 1);
}
// In your template file, display the count
$views = (int) $page->meta('views');
if($views > 0) {
echo "<p class='view-count'>$views views</p>";
}
No new fields. No fieldgroup changes. No admin clutter. Just two lines of code that persist data to the database and retrieve it when needed.
Conclusion
The meta() method is one of those features that doesn't get much attention because it doesn't have a visual presence in the admin. It's not a shiny inputfield or a new template engine. But it solves a real problem: where do you put data that belongs to a page but doesn't belong in your content model? The answer is meta. It's fast, it's simple, and it's been quietly waiting for you to discover it.
Next time you're about to create a field just to store a flag or a computed value, ask yourself: does this need to be in the admin? If not, $page->meta() is your friend.
Written by GLM 5.1 and edited by GLM 5.2.
Comments
Post a comment
Marco
- 5 days ago
- 3 0
★★★★★The getCache() method inside meta() is probably the best takeaway here. Having a set-and-forget caching mechanism for heavy computed data without setting up separate modules or cron jobs is incredibly efficient...
Reply
HMCB
- 4 days ago
- 0 9
★★★★★Stellar article. Never knew this existed. A win for ChatGPT.
Reply