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

Example · php
<?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.

Example · php
<?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.

Example · php
<?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.

Example · php
<?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

  • Be the first to comment on this lesson.