From WordPress Website to a Full-Stack PHP Application: Designing the Architecture
When we began building the new version of sanjibsinha.in, the obvious solution would have been to install WordPress, choose a theme, add some plugins, and start publishing.
That would have worked.
But it would not have been particularly interesting.
The goal of this project was different. I wanted to explore what happens when WordPress is treated not as the complete website, but as one part of a larger application architecture.
The result is a system in which WordPress manages content and application data, a custom WordPress plugin exposes that data through an API, and a lightweight PHP frontend is responsible for presenting it to visitors.
The project therefore sits somewhere between a conventional WordPress website and a custom PHP application.
This article explains how we arrived at that architecture and, more importantly, why we chose it.
The Starting Point: WordPress Is Extremely Good at Being a CMS
WordPress has an enormous advantage that is easy to underestimate.
It already gives us a mature content-management system.
We get:
- an administration dashboard
- users and permissions
- posts and pages
- categories and taxonomies
- media management
- revisions
- metadata
- a database abstraction layer
- a REST API
- a huge ecosystem of extensions
There was no good reason to throw all of that away simply because we wanted a custom frontend.
Instead, we asked a different question:
What if WordPress handled the content and application data while we built the public-facing application ourselves?
That question became the foundation of the project.
Separating Content From Presentation
A traditional WordPress installation generally looks something like this:
Browser
β
WordPress
β
Theme
β
HTML
β
Browser
WordPress controls almost everything.
The theme determines how the content is presented, WordPress determines how the request is processed, and PHP templates inside the theme generate the final HTML.
That model is perfectly valid.
But it creates a strong relationship between the CMS and the presentation layer.
We wanted more freedom.
Our conceptual architecture became:
ββββββββββββββββββββββββ
β WordPress β
β β
β CMS β
β Content β
β Applications β
β Media β
β Database β
ββββββββββββ¬ββββββββββββ
β
β REST API
βΌ
ββββββββββββββββββββββββ
β Custom API Layer β
β β
β API endpoints β
β Data transformation β
β Validation β
β Responses β
ββββββββββββ¬ββββββββββββ
β
β JSON
βΌ
ββββββββββββββββββββββββ
β PHP Frontend β
β β
β Routing β
β Templates β
β HTML β
β CSS β
β JavaScript β
ββββββββββββ¬ββββββββββββ
β
βΌ
Browser
The important idea is the boundary between WordPress and the frontend.
WordPress becomes the content and application backend.
The frontend becomes the presentation layer.
And the API becomes the contract between them.
Why Not Simply Use the WordPress REST API?
WordPress already has a REST API.
So why build another API layer?
Because the default WordPress API exposes WordPress’s internal content model. That is useful, but it is not necessarily the API we want our application to depend on.
Suppose the frontend wants a collection of applications.
The frontend does not really care that an application happens to be represented internally as a WordPress custom post type called app_repository.
It wants something conceptually simpler:
{
"id": "example-app",
"title": "Example Application",
"version": "1.0.0",
"description": "An example application.",
"os": "Linux / Ubuntu",
"file": "https://example.com/example.zip"
}
This is an important architectural distinction.
The backend can have one internal representation while the frontend receives another.
The API therefore acts as a translation layer.
That gives us room to change the internal WordPress implementation later without forcing the frontend to understand every WordPress-specific detail.
The Custom WordPress Plugin
The backend therefore became more than an ordinary WordPress installation.
We created a custom plugin called Full Stack App.
Its responsibility is to provide the application layer on top of WordPress.
The basic structure looks like this:
fullstack-app/
β
βββ fullstack-app.php
βββ composer.json
βββ app/
β βββ Bootstrap.php
β βββ Api/
β βββ Registrar.php
β βββ Rest.php
β βββ Status.php
β βββ Posts.php
β βββ Categories.php
β βββ Apps.php
β βββ ApiResponse.php
β βββ ApiError.php
β
βββ tests/
βββ bin/
βββ vendor/
This structure is deliberately closer to an application component than to a collection of miscellaneous WordPress functions.
The plugin boots its application code, registers the API modules, and exposes endpoints under its own namespace:
fullstack/v1
For example:
/wp-json/fullstack/v1/posts
/wp-json/fullstack/v1/categories
/wp-json/fullstack/v1/apps
The exact endpoints can evolve as the application grows, but the principle remains the same:
WordPress stores and manages the information. The custom application layer decides how that information is exposed.
Applications Are Content Too
One of the more interesting decisions in this project was to treat downloadable applications as a first-class content type.
Instead of creating a completely separate database system for applications, we created a custom post type:
app_repository
This gives WordPress an administrative interface for managing applications.
An application can have information such as:
- title
- description
- version
- operating system
- featured image
- downloadable ZIP file
- additional metadata
From the administrator’s point of view, it is simply another thing managed through WordPress.
From the frontend’s point of view, however, it becomes an application in a software repository.
This is one of the strengths of the architecture.
The CMS does not need to understand the entire visual presentation of the repository.
It only needs to manage the underlying information.
The API Becomes the Contract
A useful way to think about the architecture is to imagine that WordPress and the frontend are two separate teams.
The WordPress side says:
“Here is the data I can provide.”
The frontend says:
“Here is the data I need.”
The API sits between them and establishes the agreement.
For example, the posts endpoint supports things such as:
page
per_page
search
category
orderby
order
The backend can therefore perform filtering, pagination and sorting while returning a predictable application-oriented response.
The response itself follows a consistent structure:
{
"data": [],
"meta": {
"page": 1,
"per_page": 10,
"total": 25,
"total_pages": 3
}
}
That consistency matters.
Once the frontend understands the contract, it does not need to know how WordPress performed the query.
The Frontend Is Deliberately Lightweight
On the other side of the architecture is the public-facing PHP application.
Instead of relying on a large frontend framework, we deliberately kept the stack small:
PHP
HTML
CSS
JavaScript
The frontend contains separate entry points for different kinds of content, including:
index.php
article.php
application.php
page.php
Shared presentation elements are kept in:
includes/
header.php
footer.php
while the visual layer lives in:
assets/
style.css
script.js
This gives us a conventional PHP application structure without requiring a large JavaScript runtime.
There is something refreshing about this approach.
The browser receives HTML.
CSS handles presentation.
JavaScript handles the small amount of interaction that actually needs JavaScript.
PHP handles server-side rendering and application logic.
There is no requirement to turn every page into a JavaScript application.
Why PHP?
The choice of PHP was intentional.
WordPress itself is PHP.
The backend is PHP.
The frontend is PHP.
That means we can work within one language across the entire application while still keeping the responsibilities separated.
It also gives us a very practical deployment model.
A conventional PHP-capable server can run the frontend without requiring a large Node.js-based production environment.
For a content-heavy website, repository, documentation system or similar application, this can be an attractive trade-off.
The frontend can remain small, understandable and fast.
Performance Was Part of the Architecture
Performance was not treated as a final optimization step.
It influenced the architecture from the beginning.
A public-facing website does not necessarily need to execute an enormous amount of frontend JavaScript simply to display a title, an excerpt and a button.
Our frontend therefore follows a relatively simple model:
Request
β
PHP
β
Data
β
HTML
β
Browser
Rather than:
Request
β
JavaScript application
β
API requests
β
State management
β
Component rendering
β
Hydration
β
Browser
There are situations where the second model is absolutely appropriate.
This project did not require it.
The architecture was designed around the actual problem rather than around the fashionable technology of the moment.
Clean URLs Belong to the Frontend
Another useful separation is URL design.
The public website should expose URLs that make sense to human beings:
/
/articles/my-article
/applications/my-application
/about
/contact
The visitor does not need to know anything about WordPress.
They should not have to see URLs such as:
/wp-admin/
/wp-content/
/wp-json/
/?p=123
Those belong to the backend.
The frontend owns the public URL structure.
This is another reason to separate presentation from content management.
WordPress Does Not Need to Render the Public Website
This architectural decision also changes the role of WordPress.
In a conventional installation, WordPress is both:
CMS + Application + Template Engine + Public Website
In our architecture, we are moving toward:
WordPress
β
CMS + Data + Administration + API
PHP Frontend
β
Public Website + Presentation + URLs
That is the essence of the headless approach.
The CMS becomes a service consumed by another application.
But Headless Does Not Mean “Throw WordPress Away”
This distinction is important.
Going headless is not about rejecting WordPress.
It is about using WordPress for the parts where WordPress is exceptionally useful while taking control of the parts where a custom application gives us more flexibility.
We still get:
- WordPress administration
- familiar content editing
- media management
- users
- taxonomies
- custom post types
- database infrastructure
- WordPress APIs
But the public interface does not have to be constrained by a WordPress theme.
That is the real attraction.
The Architecture Is Also Reusable
Although the initial implementation was built for sanjibsinha.in, we deliberately avoided making every piece dependent on the website itself.
The backend plugin can expose reusable application data.
The frontend contains reusable patterns for:
- layouts
- navigation
- articles
- applications
- pages
- metadata
- downloads
- pagination
This opens another possibility.
Instead of building one website from scratch every time, we can eventually build several applications on top of the same architectural foundation.
For example:
Shared Architecture
β
ββββββββββββββββββΌβββββββββββββββββ
β β β
βΌ βΌ βΌ
Blog Magazine App Repository
β β β
βΌ βΌ βΌ
PHP Frontend PHP Frontend PHP Frontend
β β β
ββββββββββββββββββΌβββββββββββββββββ
β
βΌ
WordPress API
That is where this project begins to become more than a website.
It becomes a reusable development pattern.
What We Have Actually Built
At this stage, the architecture can be summarized in five layers.
1. WordPress
Responsible for content management and administration.
2. Custom Plugin
Responsible for application-specific backend behavior and API endpoints.
3. REST API
Responsible for creating a predictable boundary between backend data and frontend presentation.
4. PHP Frontend
Responsible for public pages, URLs, rendering and presentation.
5. CSS and JavaScript
Responsible for the visual experience and lightweight browser-side interaction.
The resulting system can be visualized like this:
USER
β
βΌ
βββββββββββββββ
β Browser β
ββββββββ¬βββββββ
β
βΌ
βββββββββββββββ
β PHP Frontendβ
β β
β HTML β
β CSS β
β JavaScript β
ββββββββ¬βββββββ
β
β API contract
βΌ
βββββββββββββββ
β Custom WP β
β Plugin β
ββββββββ¬βββββββ
β
βΌ
βββββββββββββββ
β WordPress β
β β
β CMS β
β Database β
β Media β
βββββββββββββββ
The beauty of the architecture is not that any individual component is exotic.
None of the technologies are particularly mysterious.
The interesting part is how the responsibilities are divided.
The Beginning of Something Larger
This project started with a fairly simple requirement:
Build a fast, maintainable website without being tied to a conventional WordPress theme.
But the experiment raised a more interesting question:
Can WordPress become the backend of a reusable PHP application rather than merely the engine behind a WordPress website?
That question led us to the architecture described here.
And once the architecture existed, two separate pieces naturally emerged:
- a WordPress backend/plugin package, and
- a PHP frontend package.
The backend provides the content and application API.
The frontend provides the public-facing application.
That separation means each part can be developed, tested and eventually reused independently.
In the next article, we will move from architecture to implementation and examine the first of those two pieces:
how we built the custom WordPress plugin that turns WordPress into the backend of the application.
That is where the architecture stops being a diagram and starts becoming code.