Craft CMS for WordPress Developers: A Translation Guide
I spent the first decade of my career building WordPress sites. So when I talk to WordPress developers who are curious about Craft, I know exactly where the confusion comes from. The two systems solve the same problem but think about it in completely different ways.
This is the post I wish someone had written for me when I made the switch. Not a "why Craft is better" pitch, but an honest map from one system to the other so you can start being productive quickly.
The Concept Map
Let's start with terminology. Almost everything you know from WordPress has an equivalent in Craft, just with a different name and sometimes a different shape.
The Biggest Mental Shift: Content Modeling
In WordPress, content modeling is an afterthought. You start with "posts" and "pages," and then you bolt on custom fields using ACF or custom post types using a plugin. The default content model is a blog, and everything else is an extension of that.
In Craft, content modeling is the starting point. Before you write a single line of template code, you sit down and design your sections, entry types, and fields in the control panel. There's no default blog structure. You build exactly what you need.
This feels backwards at first if you're used to WordPress. But it means your content model is always intentional. You never end up with a mess of post meta fields and taxonomies that grew organically over five years.
Template Comparison
The biggest day-to-day difference is the template language. WordPress uses raw PHP. Craft uses Twig, which is a proper template engine with cleaner syntax and automatic output escaping.
Listing Posts
<?php
$args = [
'post_type' => 'post',
'posts_per_page' => 10,
'orderby' => 'date',
'order' => 'DESC',
];
$query = new WP_Query($args);
if ($query->have_posts()) :
while ($query->have_posts()) : $query->the_post();
?>
<article>
<h2><a href="<?php the_permalink(); ?>">
<?php the_title(); ?>
</a></h2>
<time><?php echo get_the_date(); ?></time>
<div><?php the_excerpt(); ?></div>
</article>
<?php
endwhile;
wp_reset_postdata();
endif;
?>
{% set posts = craft.entries
.section('blog')
.limit(10)
.orderBy('postDate DESC')
.all() %}
{% for post in posts %}
<article>
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
<time>{{ post.postDate|date('F j, Y') }}</time>
<div>{{ post.body|truncate(200) }}</div>
</article>
{% endfor %}
A few things to notice:
- No global state. In WordPress,
the_post()sets up a global object that template tags read from. In Craft, you're working with a regular variable in a regular loop. Nothing hidden. - No cleanup needed. No
wp_reset_postdata()to worry about. The Twig loop just ends. - Output is auto-escaped. In Twig,
{{ post.title }}is automatically escaped for HTML. In WordPress, you have to remember to useesc_html()or risk XSS vulnerabilities.
Custom Fields
In WordPress with ACF:
<?php
$subtitle = get_field('subtitle');
$hero_image = get_field('hero_image');
$cta_link = get_field('cta_link');
if ($hero_image) :
?>
<img src="<?php echo esc_url($hero_image['url']); ?>"
alt="<?php echo esc_attr($hero_image['alt']); ?>">
<?php endif; ?>
<h2><?php echo esc_html($subtitle); ?></h2>
<?php if ($cta_link) : ?>
<a href="<?php echo esc_url($cta_link['url']); ?>">
<?php echo esc_html($cta_link['title']); ?>
</a>
<?php endif; ?>
In Craft:
{% set image = entry.heroImage.one() %}
{% if image %}
<img src="{{ image.url }}" alt="{{ image.alt }}">
{% endif %}
<h2>{{ entry.subtitle }}</h2>
{% if entry.ctaLink.url %}
<a href="{{ entry.ctaLink.url }}">{{ entry.ctaLink.text }}</a>
{% endif %}
The difference in readability is significant. Craft fields are accessed as properties on the entry object. No separate function calls, no separate escaping. And because Craft has first-class field types for links, assets, entries, and more, you don't need a separate plugin just to have structured custom fields.
Template Includes and Layouts
WordPress uses get_template_part() and a system of header.php, footer.php, and sidebar.php files. Craft uses Twig's template inheritance, which is cleaner and more flexible.
<?php get_header(); ?>
<main>
<?php get_template_part('template-parts/content', get_post_type()); ?>
</main>
<?php get_sidebar(); ?>
<?php get_footer(); ?>
{# templates/blog/_entry.twig #}
{% extends "_layouts/base" %}
{% block content %}
<main>
{% include "_partials/post-card" with { entry: entry } %}
</main>
{% include "_partials/sidebar" %}
{% endblock %}
Twig's {% extends %} and {% block %} system means your base layout defines the structure and child templates fill in the content. No more duplicating get_header() and get_footer() in every file. The layout handles it all in one place.
Template Routing: A Key Difference
In WordPress, the template hierarchy is implicit. WordPress looks at the URL and automatically picks a template file based on naming conventions (single-post.php, archive-product.php, etc.). You don't configure this. You just name files correctly and WordPress finds them.
In Craft, you explicitly tell each section which template to use. Go to Settings > Sections, pick your section, and set the template path. If the blog section uses blog/_entry, then Craft will render templates/blog/_entry.twig for every blog entry.
This is more explicit but also more flexible. You can point any section at any template. You can change the template assignment without renaming files. And you never have to wonder "which template is WordPress actually using for this page?" because you set it yourself.
Things WordPress Does That Craft Doesn't
Craft is not trying to be WordPress, and there are some things WordPress includes out of the box that Craft handles differently:
- No built-in commenting system. Craft doesn't include comments. If you need them, you'd use a third-party service like Disqus or build a custom solution.
- No user-facing registration by default. Craft's user system is designed for content authors, not site visitors. You can enable front-end registration, but it's not the default.
- No built-in REST API for everything. WordPress exposes every post type via REST by default. Craft gives you GraphQL (Pro) and Element API (plugin), but you opt in rather than having it on by default.
- No theme marketplace. There's no equivalent to the WordPress theme directory. Every Craft site is custom-built from scratch, which is the point.
Things Craft Does Better
And here's where Craft shines compared to WordPress, based on my experience with both:
- Content modeling. Building complex, structured content in Craft is so much easier than wrestling with ACF field groups, meta boxes, and custom post types in WordPress. It's all built in and it all works together.
- Matrix fields. The equivalent of Gutenberg blocks or ACF Flexible Content, but they've been in Craft since version 1 and they're deeply integrated. Content editors love them.
- Image handling. Craft's asset system with focal points, named transforms, and automatic format conversion is years ahead of WordPress's media library.
- Live Preview. Craft's Live Preview shows content editors their changes in real time, side by side with the editing interface. WordPress has a preview button, but the experience isn't as polished.
- Multi-site. Running multiple sites from one Craft installation is a first-class feature. WordPress Multisite works but it's always felt like it was bolted on after the fact.
- Security. Craft uses Composer for dependency management, doesn't have a massive plugin ecosystem that's constantly being exploited, and doesn't broadcast what CMS it's running. The attack surface is much smaller.
- No database surprises. Craft uses real database tables with proper schemas. WordPress stores everything in
wp_postmetaas key-value pairs, which leads to slow queries on large sites.
Local Development
If you're used to Local by Flywheel or MAMP for WordPress, the Craft equivalent is DDEV. It's a Docker-based local dev tool that works great with Craft.
# Set up a new Craft project with DDEV
ddev config --project-type=craftcms --docroot=web
ddev start
ddev composer create craftcms/craft
DDEV gives you PHP, MySQL/Postgres, Redis, and everything else Craft needs in a containerized environment. Each project gets its own isolated setup so there's no version conflict between projects.
Plugin Management
WordPress plugins live in wp-content/plugins/ and you install them from the admin dashboard or by dropping in files. Craft plugins are Composer packages. You install them from the command line:
composer require nystudio107/craft-seomatic
php craft plugin/install seomatic
This feels different coming from WordPress, but it's objectively better for a few reasons:
- Composer handles dependency resolution. If two plugins need different versions of a library, Composer will tell you before anything breaks.
- Your
composer.jsonandcomposer.lockfiles are a version-controlled record of exactly which plugins and versions your site uses. - No downloading ZIP files or FTP-ing folders. Your deployment process can run
composer installand get exactly the right plugins every time.
Common Craft Plugins for WordPress Converts
Here are the Craft plugins that fill the same roles as popular WordPress plugins you might be used to:
- Yoast SEO → SEOmatic by nystudio107. More powerful than Yoast, with full control over meta tags, structured data, and sitemaps.
- Contact Form 7 / Gravity Forms → Freeform by Solspace. Full-featured form builder with conditional logic and integrations.
- WooCommerce → Craft Commerce. First-party e-commerce plugin built by the Craft team.
- WPML → Built-in Multi-Site. Craft's multi-site feature handles multi-language natively.
- Redirection → Retour by nystudio107. Tracks 404s and manages redirects.
- W3 Total Cache / WP Rocket → Blitz by PutYourLightsOn. Static page caching for Craft.
- ACF → Built-in. There is no ACF equivalent because Craft's field system does everything ACF does, and it's built into the core.
Database and Deployment
WordPress stores its settings in the database, which means migrating changes between environments (dev, staging, production) is a pain. You end up either syncing databases or using a plugin like WP Migrate DB.
Craft has a feature called Project Config that solves this completely. All your structural settings (sections, fields, entry types, plugin settings) are stored as YAML files in a config/project/ directory. These files get committed to Git, and when you deploy, you run php craft project-config/apply to sync the changes to the database.
This means your content model is version controlled. You can review changes in pull requests. You can roll back a bad change with git revert. It's a game changer compared to the WordPress workflow.
# Deploy workflow
git pull origin main
composer install --no-dev
php craft project-config/apply
php craft up
The Learning Curve
I won't pretend there's no learning curve. If you've been building WordPress sites for years, some things in Craft will feel unfamiliar:
- Twig takes a few days to click. If you're used to mixing PHP and HTML, Twig's syntax feels restrictive at first. But once it clicks, you'll appreciate how clean it keeps your templates.
- Composer feels like a lot. If you've never used the command line for package management, there's a learning curve. But it's the same tool that Laravel, Symfony, and most modern PHP frameworks use, so it's worth learning.
- Content modeling upfront takes discipline. WordPress lets you start building immediately and add structure later. Craft encourages you to plan your content model first. This is better in the long run but slower at the start.
For most WordPress developers I've worked with, it takes about a week to feel comfortable and about a month to feel fully productive. After that, most of them don't want to go back.
If you're a WordPress developer thinking about trying Craft, just build something small. A personal blog, a portfolio site, anything. The best way to learn is to build, and the Craft docs are genuinely good. And if you're an agency looking to transition your team from WordPress to Craft, I can help with that process.