WordPress Introduction
What WordPress actually is, how WordPress.org differs from WordPress.com, and where it fits for a developer who already knows PHP.
WordPress is a GPL-licensed PHP application that keeps its content in MySQL or MariaDB and builds each page on request. You do not write a WordPress site by editing HTML files; you write PHP that runs inside WordPress's request cycle and returns markup. This course covers WordPress 6.8 running on PHP 8.2 or newer.
.org versus .com
WordPress.org is the software: an archive you download, install on your own server, and control completely. WordPress.com is a hosting product from Automattic that runs the same software but restricts what you may install — custom plugins and themes are only available on its higher paid plans. Every line of code in this course targets self-hosted WordPress from .org.
Why it is everywhere
W3Techs' running survey puts WordPress at roughly 40% of all websites and close to 60% of the sites using any recognised CMS. In practice that means inherited codebases, client work, and a plugin ecosystem large enough that most problems have already been solved — often badly, twice.
Who it is for
WordPress suits content-driven sites where non-developers publish: marketing sites, publications, documentation, shops via WooCommerce. It is a poor fit when your data is mostly relational with no editorial workflow — you end up fighting the posts table instead of using a schema.
What trips people up
Coming from a framework, the instinct is to look for a router, a controller and a service container. There are none. WordPress runs a fixed request sequence and calls your functions at named points along it. Learning WordPress is mostly learning those points.
Example
<?php
/**
* Plugin Name: Reading Time
* Description: Appends an estimated reading time to single post content.
* Version: 1.0.0
* Requires at least: 6.8
* Requires PHP: 8.2
* License: GPL-2.0-or-later
*/
// Standard guard: stop if the file was requested directly instead of
// being loaded by WordPress. ABSPATH is defined during the bootstrap.
defined( 'ABSPATH' ) || exit;
/**
* WordPress never calls this file's code directly. You register a callback
* on a named point in the request ('the_content') and core calls it for you.
*/
add_filter( 'the_content', 'rt_prepend_reading_time' );
function rt_prepend_reading_time( $content ) {
// 'the_content' fires for excerpts, widgets and REST output too, so narrow
// it down to the main post body on a single post view.
if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
return $content;
}
$words = str_word_count( wp_strip_all_tags( $content ) );
$minutes = max( 1, (int) ceil( $words / 200 ) );
$notice = '<p>' . sprintf(
/* translators: %d: estimated reading time in minutes. */
esc_html__( 'About %d minute read', 'reading-time' ),
$minutes
) . '</p>';
// A filter must return a value. Returning nothing wipes the post body.
return $notice . $content;
}When to use it
- Deciding whether a client project should be self-hosted WordPress or a hosted .com plan.
- Explaining to a stakeholder why the site cannot install a custom plugin on its current .com plan.
- Judging whether an incoming brief is a good fit for WordPress or wants a real application framework.
More examples
Refusing to run on an unsupported install
Shows the two ways to read the WordPress version and why 6.7 added a second one. Reach for this pattern in any plugin you distribute: WordPress will honour the 'Requires at least' and 'Requires PHP' headers at activation time, but a runtime guard also covers sites that downgrade PHP after activation.
<?php
/**
* Plugin Name: Acme Sync
*/
defined( 'ABSPATH' ) || exit;
// wp_get_wp_version() was added in WordPress 6.7 and returns the real version
// even when a plugin has overwritten the $wp_version global for obscurity.
if ( function_exists( 'wp_get_wp_version' ) ) {
$acme_wp = wp_get_wp_version();
} else {
// Works everywhere, but the value passes through a filter first.
$acme_wp = get_bloginfo( 'version' );
}
// PHP_VERSION_ID is an integer: 80200 is PHP 8.2.0.
if ( PHP_VERSION_ID < 80200 || version_compare( $acme_wp, '6.8', '<' ) ) {
add_action( 'admin_notices', static function () {
printf(
'<div><p>%s</p></div>',
esc_html__( 'Acme Sync needs WordPress 6.8 and PHP 8.2.', 'acme' )
);
} );
return; // A top-level return stops the rest of this file loading.
}Echoing shorthand and the longhand it desugars to
You will meet both forms constantly in other people's themes. Knowing that the_x() is echo get_the_x() means you can always swap to the returning version when you need to inspect or modify a value before printing it, instead of buffering output.
<?php
// WordPress ships most template tags as a pair: a 'the_' function that echoes,
// and a 'get_the_' function that returns. The short one is literally the long
// one wrapped in echo.
the_title(); // shorthand
echo get_the_title(); // the longhand it desugars to
// the_permalink() adds escaping and its own filter on top of the getter:
the_permalink();
echo esc_url( apply_filters( 'the_permalink', get_permalink(), 0 ) ); // same output
// Translation functions follow the same rule. These two lines are equivalent:
esc_html_e( 'Read more', 'my-theme' ); // shorthand
echo esc_html__( 'Read more', 'my-theme' ); // longhand
// Use the returning form whenever you need the value rather than the output.
$title = get_the_title();
if ( str_starts_with( $title, 'Draft: ' ) ) {
$title = substr( $title, 7 );
}
echo esc_html( $title );Where your code can live
Registering a post type in functions.php is the classic beginner mistake: switch themes and the content becomes unreachable. Use this to decide where a snippet goes — anything that owns data or business logic is a plugin, not a theme file.
<?php
// Three homes for the same snippet, in load order:
//
// 1. wp-content/mu-plugins/000-acme.php — always active, cannot be switched
// off in the admin, loaded before normal plugins. No activation hooks run.
//
// 2. wp-content/plugins/acme/acme.php — activated per site, survives a
// theme change. Where behaviour belongs.
//
// 3. wp-content/themes/acme/functions.php — dies with the theme. Presentation
// only.
// The same registration works in all three; only the lifetime differs.
add_action( 'init', static function () {
register_post_type( 'acme_case_study', array(
'public' => true,
'label' => 'Case Studies',
'has_archive' => true,
'show_in_rest' => true, // Required for the block editor and the REST API.
) );
} );
Discussion