Building a Headless CMS API with ProcessWire
Organizing Content Delivery via ProcessWire API
A ProcessWire project has been running for several years, with dozens of templates accumulated. Then the client demands a React SPA or mobile app. Standard PHP rendering won't work—they need clean JSON. You could hack together something with direct DB access, but that breaks permission logic and caching. The right solution is to add an HTTP API on top of the existing templates. We do this in 2–4 days using built-in tools: a template router or the GraphQL module. No rewriting—just a new endpoint.
We have completed over 30 such projects. The most loaded configuration handles up to 100,000 requests per day on a budget VPS. The API has been running without issues for over two years. Below we break down the implementation options.
Built-in PHP API
ProcessWire exposes $pages, $page, $user, and $config as global variables in templates:
// templates/blog.php
// Selection with filters and pagination
$limit = 12;
$start = ($input->pageNum - 1) * $limit;
$posts = $pages->find("template=blog-post, status=published, sort=-date, limit=$limit, start=$start");
$totalPosts = $pages->count("template=blog-post, status=published");
// Selection with field conditions
$featuredPosts = $pages->find("
template=blog-post,
featured=1,
date>=today,
category.name%=Web Development,
sort=-date,
limit=3
");
// Single item
$post = $pages->get("template=blog-post, slug=my-post-slug");
if (!$post->id) wire404();
For a full ProcessWire JSON API, we create custom REST endpoints using ProcessWire API templates, enabling seamless headless ProcessWire SPA development.
Selector String — Query Language
// Text search
$pages->find("template=product, title*=laptop, sort=title");
// Numeric conditions
$pages->find("template=product, price>=1000, price<=5000");
// Date
$pages->find("template=event, event_date>=today, sort=event_date");
// OR conditions
$pages->find("template=post, (category=tech|category=science)");
// Related pages
$pages->find("template=product, categories.id={$category->id}");
// Full-text search
$pages->find("title|body~=search query, template=post");
// Sort by custom field
$pages->find("template=product, sort=-rating, sort=title");
REST API via ProCache or Custom Module
ProcessWire does not ship with a built-in REST API. We create one using a template router:
// site/templates/api.php
// URL: /api/blog/?page=1&limit=10
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: ' . $config->httpHost);
// Simple key-based authorization
$apiKey = $input->get->text('key');
if ($apiKey !== $config->apiKey) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
return;
}
$page_num = (int) $input->get->int('page') ?: 1;
$limit = min((int) $input->get->int('limit') ?: 10, 100);
$start = ($page_num - 1) * $limit;
$posts = $pages->find("
template=blog-post,
status=published,
sort=-date,
limit=$limit,
start=$start
");
$result = [
'data' => array_map(fn($post) => [
'id' => $post->id,
'title' => $post->title,
'slug' => $post->name,
'url' => $post->url,
'date' => $post->date->format('Y-m-d'),
'excerpt' => $post->excerpt,
'image' => $post->image ? [
'url' => $post->image->width(800)->url,
'width' => 800,
'height' => (int) round(800 / $post->image->ratio),
] : null,
], $posts->getArray()),
'total' => $posts->getTotal(),
'page' => $page_num,
'limit' => $limit,
];
echo json_encode($result);
ProcessWire GraphQL Module
Install via ProcessWire modules. Download from processwire.com/modules/processwire-graphql/ and add settings to config.php:
$config->graphql = [
'templateFilters' => ['blog-post', 'product', 'category'],
'fieldFilters' => ['title', 'body', 'date', 'image', 'category'],
'maxLimit' => 100,
];
query {
blogPost(s: "status=published, sort=-date, limit=10") {
list {
id
title
date
body
image { url(width: 800) }
category { title url }
}
total
}
}
Response Caching
// Caching via WireCache
$cacheKey = "api_posts_page{$page_num}";
$cached = $cache->get($cacheKey);
if ($cached) {
echo $cached;
return;
}
// ... build $result ...
$json = json_encode($result);
$cache->save($cacheKey, $json, 3600); // 1 hour
echo $json;
Building a basic REST API on ProcessWire (5–7 endpoints) takes 2–4 days. Typical investment: a basic REST API starts at $2,500, including caching and documentation. GraphQL integration adds $500–$1,500. Cost is determined after analyzing the project scope. Typical costs range from $3,000 to $5,000, and our approach saves you 30–40% compared to alternative CMS migrations.
REST vs GraphQL for ProcessWire
| Criterion | Custom REST | ProcessWire GraphQL |
|---|---|---|
| Implementation complexity | Medium (template router) | Low (module + config) |
| Development speed | 2–4 days | 1–2 days |
| Control over response | Full | Limited by settings |
| Typing | No | Yes (via GraphQL schema) |
| Caching level | Endpoint | Query (via persisted queries) |
REST is better when you need minimal response and full control. GraphQL is preferable if clients need flexible field selection.
ProcessWire as a Headless CMS: Advantages
Unlike WordPress (where an API is a bolt-on hack) or Strapi (heavy), ProcessWire provides a single source of truth for both PHP templates and external clients. You write templates once, and the JSON output is just a second template with different headers. According to our estimates, this reduces maintenance costs by 30–40%.
ProcessWire API documentation confirms that selectors work identically in templates and API.
What Is Included in ProcessWire API Setup
| Stage | Duration | Description |
|---|---|---|
| Design | 0.5 day | Endpoint schema, data types |
| REST implementation | 1–2 days | Template router, auth, CORS |
| Or GraphQL | 0.5–1 day | Module, config, tests |
| Caching | 0.5 day | WireCache + Redis |
| Documentation | 0.5 day | Postman or Swagger |
| Testing & deployment | 1 day | Load tests, monitoring |
Deliverables:
- API documentation (Postman/OpenAPI)
- Access to staging environment
- 1-hour training session for your developers
- 30 days of post-deployment support
- Source code and deployment scripts
Common mistakes when doing it yourself
- Incorrect CORS headers (not all methods allowed)
- Missing request limits (security vulnerability)
- Serializing the entire $page object (leaks extra data, slow response)
- No caching — every request hits the DB
We account for all of this from day one.
How We Do It: Phases
- Analysis: Study the template and field structure; determine required endpoints.
- Design: Map the data schema and decide what to expose to the client.
- Implementation: Write the template router or configure GraphQL; set up caching.
- Testing: Verify all endpoints; perform load testing (100 concurrent requests).
- Deployment: Push to production server; set up monitoring.
We guarantee the result: if the API is not working after 4 days, we finish it free of charge. Experience: 30+ ProcessWire projects.
A ProcessWire REST API is developed 2–3 times faster than on WordPress with plugins, thanks to the unified selector syntax.
Our Edge: 30+ ProcessWire API Projects
With 7+ years of ProcessWire expertise, 30+ projects, and 5+ years on the market, we have proven experience. Over the years of working with ProcessWire, we have built APIs for e-commerce platforms, SaaS services, and content portals. We know the non-obvious constraints: PermissionManager behavior in headless mode, ProCache nuances with Bearer tokens, and fieldgroup peculiarities when serializing nested objects. The most loaded configuration from our practice handles 100,000 requests per day on a 4 GB RAM server without degradation. We are ready to take on any complexity: from a basic REST API with five endpoints to GraphQL with persisted queries and real-time subscriptions. Send us a description of your project—we will propose the optimal stack and estimate the work within one business day.
With over 7 years of ProcessWire development experience and 30+ completed API projects, we ensure reliable delivery. Get a consultation for your project—contact us and we will provide an estimate within one day.







