Many freelancers and agencies treat the portfolio section as a gallery: “here is a pizza shop website, pretty, right?”. A client does not buy a website, they buy the solution to a problem. A portfolio that does not show the problem, the technical decision and the result leaves the client without the information they need to choose you.
Why a WordPress developer portfolio matters
For a WordPress developer, the portfolio is proof that you can do what you claim, and a way to show how you think, which a CV does not convey. There are plenty of WordPress freelancers on offer, so what sets you apart is the quality of what you show, not the number of projects.
What clients look for in a WordPress freelancer
- Solutions, not technologies: the client does not care that you used React, they care that you solved their kind of problem.
- Metrics and results: concrete numbers with a source, not vague phrases like “I improved performance”.
- Thought process: how you arrived at the solution and which alternatives you ruled out.
How to write a WordPress portfolio case study
Instead of a screenshot and a link, describe the process. A good portfolio is a collection of case studies, and each one answers three questions.
Challenge: the client’s problem
Do not write “I built a website”. Write the specific problem. The examples below are illustrative, not from real projects:
- “A store with 10,000 products took 8 seconds to load and lost carts along the way.”
- “The site used a huge amount of monthly bandwidth because of unoptimized images.”
- “The client lost mobile conversions because the site was not responsive.”
Why it works: the reader recognizes the problem. If they have a similar one, they know right away that you understand their situation.
Solution: what you did and why
Explain not only what you did, but why you chose that solution over another:
“Instead of standard WooCommerce, I chose headless WordPress with Next.js because the client needed:
- a load time under 1 s (Core Web Vitals),
- integration with the external API of their ERP,
- the ability to scale to 100,000 products.
Solution: WordPress as a headless CMS, a Next.js frontend, Redis cache and the Cloudflare CDN.”
This shows that you do not copy templates but decide with technical judgement. It also helps to add what you did not choose and why: a sentence like “I ruled out a caching plugin because the catalog changed every hour” says more than a list of tools.
Result: numbers with a source
This is the most important part. Show the state before and after, and say where each figure comes from:
- Google Analytics: conversions, bounce rate, cart abandonment.
- PageSpeed Insights (Core Web Vitals): LCP, INP, CLS.
- GTmetrix: load time.
- Server logs: data transfer and database queries.
A figure without a source does not survive the first question from a technical client. If you cannot prove it, do not publish it.
Case study example with an invented client
Project: online store for a furniture manufacturer. The figures in this example are invented to illustrate the format and do not come from a real project.
Challenge. The client ran a WooCommerce store with 8,000 products. The site loaded in 6 seconds, with three consequences:
- 45% cart abandonment,
- a conversion drop from 3.2% to 1.8% over a year,
- indexing problems and Core Web Vitals in red.
Solution.
- Migration to headless WordPress: WordPress as the CMS and Next.js as the frontend.
- Image optimization: AVIF, lazy loading and responsive images.
- Caching strategy: Redis for products and Cloudflare for static assets.
- Database optimization: indexes and WP_Query queries.
- CDN with automatic CSS and JS minification.
Stack: headless WordPress, Next.js, Redis, Cloudflare, WooCommerce REST API.
Result (illustrative):
- Load time: from 6 s to 0.8 s.
- Conversion: from 1.8% to 3.9%.
- Cart abandonment: from 45% to 12%.
- Core Web Vitals: all green.
A good case study also states how long the project took and who took part, because it gives the client a sense of scale. If you worked alone, say so; if you worked with a designer and another developer, name them by role.
What code to show on GitHub
If you are looking for a job at a software company, the portfolio has to show code quality.
How to publish code from private projects
Even if the project is private, you can create public samples:
- Snippets from projects: extract the best pieces and remove any client data.
- Open source contributions: to WordPress Core or to plugins.
- Side projects: small tools that show your skills.
Before you publish a snippet, check that it contains no keys, internal paths or client names.
Custom WordPress hooks
This example limits and filters the main query of a store:
/**
* Limit and filter the WooCommerce catalog query
*
* @param WP_Query $query
*/
add_action('pre_get_posts', function ($query) {
if (!is_admin() && $query->is_main_query() && is_shop()) {
$query->set('posts_per_page', 24);
$query->set('meta_query', [
[
'key' => '_stock_status',
'value' => 'instock',
'compare' => '=',
],
]);
}
});It shows that you know WordPress hooks, think about query performance and document your code. One detail that counts: do not add no_found_rows to a paginated listing, because it breaks the page count.
Modern PHP (8.1 or newer)
class ProductCache
{
public function __construct(
private Redis $redis,
private int $ttl = 3600
) {}
public function getProductData(int $productId): ?array
{
$cacheKey = "product:{$productId}";
if ($cached = $this->redis->get($cacheKey)) {
return json_decode($cached, true);
}
$product = wc_get_product($productId);
if (!$product) {
return null;
}
$data = ['id' => $productId, 'name' => $product->get_name(), 'price' => $product->get_price()];
$this->redis->setex($cacheKey, $this->ttl, json_encode($data));
return $data;
}
}It shows typed properties, constructor property promotion, dependency injection and a caching pattern. Cache only the data you need, not whole WooCommerce objects.
Gutenberg block development
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps } from '@wordpress/block-editor';
registerBlockType('my-prefix/portfolio-item', {
edit: ({ attributes }) => {
const blockProps = useBlockProps();
return (
<div {...blockProps}>
<h3>{attributes.title}</h3>
<p>{attributes.description}</p>
</div>
);
},
save: () => null, // Server-side rendering
});It shows knowledge of the block API, React inside WordPress and server-side rendering. To go further, the guide to Gutenberg blocks, patterns and Full Site Editing explains how to structure them.
Which tech stack is worth showing
Essential in 2026:
- Gutenberg (FSE): Full Site Editing and block development.
- React and Next.js: headless WordPress.
- Composer: dependency management.
- WP-CLI: automation and deployment.
- Docker: local environment; there is a guide to WordPress development with Docker.
- Git: version control, and not just push and pull but a branching strategy.
Extra points: TypeScript, GraphQL, tests (PHPUnit, Jest, Cypress) and CI/CD with GitHub Actions or GitLab CI.
What not to show in a developer portfolio
- Projects built on ready-made ThemeForest themes, unless you customized them heavily. In that case, show what you changed.
- Code without comments, even if it seems obvious to you.
- Outdated practices, such as direct SQL queries instead of WP_Query.
- Hardcoded values: show that you use constants, filters and options.
How to build a portfolio in WordPress with ACF
You do not need complex plugins: WordPress ships with what you need.
How to register a portfolio custom post type
function my_theme_register_portfolio_cpt() {
register_post_type('portfolio', [
'labels' => [
'name' => 'Portfolio',
'singular_name' => 'Project',
],
'public' => true,
'has_archive' => true,
'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
'rewrite' => ['slug' => 'portfolio'],
'show_in_rest' => true,
]);
}
add_action('init', 'my_theme_register_portfolio_cpt');ACF fields for portfolio projects
acf_add_local_field_group([
'key' => 'portfolio_fields',
'title' => 'Portfolio details',
'fields' => [
['key' => 'client_name', 'name' => 'client_name', 'label' => 'Client name', 'type' => 'text'],
['key' => 'project_year', 'name' => 'project_year', 'label' => 'Project year', 'type' => 'number'],
[
'key' => 'tech_stack',
'name' => 'tech_stack',
'label' => 'Tech stack',
'type' => 'checkbox',
'choices' => [
'wordpress' => 'WordPress',
'react' => 'React',
'nextjs' => 'Next.js',
'woocommerce' => 'WooCommerce',
],
],
[
'key' => 'metrics',
'name' => 'metrics',
'label' => 'Metrics (before and after)',
'type' => 'repeater',
'sub_fields' => [
['key' => 'metric_name', 'name' => 'metric_name', 'label' => 'Metric', 'type' => 'text'],
['key' => 'before_value', 'name' => 'before_value', 'label' => 'Before', 'type' => 'text'],
['key' => 'after_value', 'name' => 'after_value', 'label' => 'After', 'type' => 'text'],
],
],
],
'location' => [[[
'param' => 'post_type',
'operator' => '==',
'value' => 'portfolio',
]]],
]);This gives every project the same fields, so you can show the before and after comparison with the same structure in every case study. Data you do not want on the editing screen, such as internal notes or the source of each metric, goes into meta fields with an underscore prefix (_metrics_source): WordPress hides them from the custom fields panel.
Server-rendered block for the metrics
Instead of a page builder, you can render the metrics with your own block and reuse it in every case study:
// Block: portfolio metrics
register_block_type('my-prefix/portfolio-metrics', [
'render_callback' => function ($attributes) {
$metrics = get_field('metrics');
if (!$metrics) {
return '';
}
ob_start();
?>
<div class="portfolio-metrics">
<?php foreach ($metrics as $metric) : ?>
<div class="metric-item">
<span class="metric-name"><?php echo esc_html($metric['metric_name']); ?></span>
<span class="metric-before"><?php echo esc_html($metric['before_value']); ?></span>
<span class="metric-separator">to</span>
<span class="metric-after"><?php echo esc_html($metric['after_value']); ?></span>
</div>
<?php endforeach; ?>
</div>
<?php
return ob_get_clean();
},
]);single-portfolio.php template for each project
// single-portfolio.php
get_header();
?>
<article class="portfolio-case-study">
<header>
<h1><?php the_title(); ?></h1>
<p>
<?php if ($client = get_field('client_name')) : ?>
Client: <?php echo esc_html($client); ?>
<?php endif; ?>
Year: <?php echo esc_html(get_field('project_year')); ?>
</p>
</header>
<section class="solution">
<h2>Solution</h2>
<?php the_content(); ?>
</section>
<section class="results">
<h2>Results</h2>
<?php foreach ((array) get_field('metrics') as $metric) : ?>
<p>
<?php echo esc_html($metric['metric_name']); ?>:
<?php echo esc_html($metric['before_value']); ?> to
<?php echo esc_html($metric['after_value']); ?>
</p>
<?php endforeach; ?>
</section>
</article>
<?php get_footer();Always escape with esc_html(): the portfolio is your code and gets judged as such.
How to rank a developer portfolio in Google
The portfolio is not only for people: Google reads it too.
Schema.org JSON-LD markup for the portfolio
function my_theme_portfolio_schema() {
$schema = [
'@context' => 'https://schema.org',
'@type' => 'CreativeWork',
'name' => get_the_title(),
'description' => get_the_excerpt(),
'creator' => [
'@type' => 'Person',
'name' => 'Your name',
],
'datePublished' => get_the_date('c'),
'url' => get_permalink(),
];
if ($client = get_field('client_name')) {
$schema['sourceOrganization'] = [
'@type' => 'Organization',
'name' => $client,
];
}
return $schema;
}The client name only enters the markup when the field has a value, so a client who prefers anonymity appears neither on the page nor in the structured data.
How to optimize portfolio images
- Lazy loading: for everything except the first visible image.
- Modern formats: WebP or AVIF; the guide to WordPress image optimisation explains the process.
- Responsive images: different sizes for different devices.
- Descriptive alt text: “metrics dashboard before and after the migration”, not “portfolio-image-1”.
What the best developer portfolios have in common
- Concrete metrics: not “I improved performance” but “load time dropped from 5 s to 0.8 s”, with the source.
- Thought process: how the solution was reached and what was ruled out.
- Code in context: not just a GitHub link, but an explanation of the decisions.
- Visual material: architecture diagrams and before and after screenshots.
- Client feedback: one sentence signed by the client, with their permission, carries more weight than any adjective of your own.
How to show private or NDA projects in a portfolio
Many of the best projects cannot be shown as they are: the code belongs to the client or the contract forbids naming them. A case study is still possible.
- Client name: the ACF field is optional, and the CreativeWork markup only adds the organization when it has a value. Describe the sector, for example “furniture manufacturer”, and leave the name empty.
- Code: extract snippets into a public repository. A hook that optimizes WP_Query or a caching class in PHP 8.1 shows judgement without exposing the project.
- Metrics: if you did not record them during the work, recover them from Google Analytics, PageSpeed Insights, GTmetrix or the logs. If there is no reliable figure, describe the challenge and the technical decision, and do not publish a number you could not defend in front of the client.
- Permission: ask what you may publish before you write the case study, not after.
- Your own archive: keep the before and after figures, the screenshots and the client feedback for each project in your files, so you can prove them when someone questions them.
A case study without a logo, with the problem, the solution and the reasoning well explained, still answers the question that counts: why the client should choose you.
WordPress.org profile as public proof of contribution
You write a case study yourself. The WordPress.org profile (profiles.wordpress.org) is generated by the platform, which is why a technical client reads it as an independent record. Link to it from the portfolio next to your GitHub profile.
What carries the most weight on that profile are the badges. The Meta team handbook distinguishes two types: contributor badges, generally given to anyone who has contributed to a team, and team badges, given to people active in it. Each team sets its own criteria, and several badges are assigned automatically:
| Badge | How you get it, according to the Meta handbook |
|---|---|
| Core Contributor | Automatically, though not instantly, when you receive props in a core commit |
| Plugin Developer | Automatically, when you have a plugin in the directory |
| Theme Developer | Automatically, when you have a theme in the directory |
| WordCamp Speaker | Automatically, based on speaker status on WordCamp.org, with a WordPress.org username |
The rest are assigned by hand by each team. The criteria vary: the Openverse team handbook, for example, asks you to meet at least one of several conditions, among them opening five or more valid issues, getting a substantial pull request merged or translating 25 strings. A Plugin Developer badge does not say whether the plugin is good, but it does say that it passed someone else’s review, and that is more than a screenshot proves.
What is Five for the Future and how it counts in a portfolio
Five for the Future started with a post by Matt Mullenweg on 30 September 2014. The sentence behind the program’s name says that organizations that want to grow the WordPress pie, not just their own slice, “should dedicate 5% of their people to working on something to do with core”, be it development, documentation, security, support forums, theme reviews, training, testing or translation (Matt Mullenweg, “Five for the Future”).
Today the program lives at wordpress.org/five-for-the-future. Both organizations and individuals can pledge: organizations appear on the pledges page, and individual contributors are shown with their name, a photo, a short bio and the sponsoring organization, if there is one. For a freelancer, a pledge of weekly hours to a specific team (Core, Polyglots, Training, Documentation) is a verifiable line in the portfolio. For an agency, appearing in the list of organizations is a way to show that it contributes to the project it makes its living from.
How to publish a plugin in the WordPress.org directory
A plugin approved for the directory is a code sample reviewed by third parties and used by real people. The plugin handbook describes the process:
- Register on WordPress.org with an email address you check; if you submit on behalf of a company, use an official company address.
- Submit a short description and a complete zip of the plugin, the same one you would install by hand from the dashboard.
- Wait for the review: the team reviews the code within 14 business days and contacts you if it finds problems.
- After approval you get access to a Subversion repository; the plugin appears in the directory once you upload the code and the readme via SVN.
Two details matter for the portfolio. The plugin URL cannot be changed after submission, although the display name can. And every plugin in the directory has its own support forum, so your answers to users are public and become part of what a client can read about you.
The detailed plugin guidelines number 18. The ones behind most rejections: a GPL or compatible license (guideline 1), code that is mostly human-readable (4), no functionality locked behind payment (5), no contacting external servers without explicit consent (7) and no third-party trademarks as the first term of the slug (17). If your plugin meets them, you already have a technical argument for the case study.
GitHub profile README and pinned repositories
GitHub’s documentation sets four conditions for a README to appear on your profile: a repository with the same name as your username, public, with a README.md file in the root, and with some content. Use it as an index: what you do, a link to the portfolio, a link to your WordPress.org profile and the two or three repositories that represent you best.
You can also pin to your profile up to six repositories and gists in total. Pin the ones that match your case studies, such as the directory plugin, the repository of anonymized client snippets or the block theme, and not forks without changes.
Learn WordPress to fill gaps in your stack
If, while preparing the portfolio, you notice something from the stack list is missing, Learn WordPress organizes its content into three pathways (using, developing and designing with WordPress), with courses, lessons and live workshops. In October 2026 the Beginner WordPress Developer course is listed at 9 hours and 59 lessons, and Intermediate Theme Developer at 8 hours and 32 lessons. A finished course does not replace a case study; what goes into the portfolio is the block or theme you build with what you learned.
Where to start building your portfolio
- Choose 5 to 7 projects with a result you can measure.
- Gather the data: metrics with a source, screenshots and, if the client allows it, a sentence from them.
- Write the first case study with the challenge, solution and result structure. Budget about two hours.
- Prepare the code samples on GitHub, without client data and with PHP 8.1 or newer.
- Set up the CPT with ACF and publish the case studies in the same format.
- Review the GitHub code whenever you add a project: a repository full of old syntax weakens the case study that links to it.
The portfolio is not a CV: every project has to answer why the client should choose you. For a published case study with this structure, see the WooCommerce optimization case study.







