Week 17: WordPress Fundamentals & wp-cli

Module 2 built an application from scratch with Laravel. Module 3 works inside a different kind of PHP codebase entirely — WordPress, which still powers a huge share of the web, with its own conventions, hooks and database schema. This week is orientation: the core concepts, a proper local setup, and what WordPress's tables actually look like under the hood.

Module 16 of 28 Week 17 of 32 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Explain WordPress's core concepts: posts, pages, themes, plugins & the admin
  • Set up a local WordPress environment with wp-cli
  • Read the wp_ database schema WordPress generates

1. Core Concepts

WordPress organizes content around a few core ideas that everything else — themes, plugins, the block editor — builds on top of:

  • Posts — chronological content (blog entries), but also the underlying storage mechanism for nearly everything else via Custom Post Types (Week 19)
  • Pages — non-chronological content (About, Contact) — structurally a post with a different post_type
  • Themes — control how content is presented: templates, styles, layout
  • Plugins — extend or change behavior without modifying WordPress core or the active theme
  • The admin (/wp-admin) — the back-office UI for managing all of the above

A crucial distinction worth internalizing now: WordPress itself, every theme, and every plugin are all just PHP files following WordPress's conventions — there's no hard boundary at the language level, only a strong convention that themes handle presentation and plugins handle behavior, kept separate so either can be swapped independently.

2. Local Setup

WordPress needs PHP, MySQL and a web server — the same LAMP-style stack from Module 1, just running someone else's (very large) PHP application instead of your own. Local (by WP Engine) is the fastest way to get a working environment without manually configuring each piece:

terminal — manual alternative, if you'd rather not use Local
# Download WordPress core
curl -O https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz

cd wordpress
php -S localhost:8000
# Then visit http://localhost:8000 and follow the famous "5-minute install"

Either way, you'll end up with a wp-config.php holding database credentials (the same pattern as Week 9's Laravel .env, just an older convention — plain PHP constants instead of an environment file) and a running MySQL database WordPress manages the schema for automatically.

3. wp-cli

wp-cli is WordPress's official command-line tool — for anything beyond trivial changes, it's dramatically faster than clicking through /wp-admin:

terminal
# Install (one-time, per machine)
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

# Run from inside a WordPress installation directory
wp core version
wp plugin list
wp theme activate twentytwentyfour
wp post create --post_title="Hello from wp-cli" --post_status=publish
wp user create ada ada@example.com --role=administrator
wp db export backup.sql

wp-cli becomes essential from Week 20 onward — scaffolding plugins, running database operations during development, and scripting repetitive setup tasks are all faster and more reliable through it than through the admin UI.

4. The wp_ Database Schema

WordPress creates around a dozen tables by default, all prefixed wp_ (configurable, and changing it from the default is a common security recommendation covered in Week 24). The core ones:

  • wp_posts — every post, page, and (via post_type) every custom post type row, all in one table
  • wp_postmeta — arbitrary key/value metadata attached to posts (a classic Entity-Attribute-Value pattern — flexible, but not normalized the way Week 6 would design it from scratch)
  • wp_users & wp_usermeta — the same posts/postmeta pattern, for users
  • wp_terms, wp_term_taxonomy, wp_term_relationships — categories, tags & custom taxonomies (Week 19)
  • wp_options — sitewide settings, one row per named option
exploring it directly
wp db query "SELECT ID, post_title, post_type, post_status FROM wp_posts LIMIT 10"
wp db query "SELECT meta_key, meta_value FROM wp_postmeta WHERE post_id = 1"
Never query wp_ tables directly in real code

WordPress provides a full API — WP_Query, get_post_meta(), update_option() — for every operation on these tables, and it handles caching, hooks, and edge cases raw SQL wouldn't. Direct queries here are for exploration and understanding only; Weeks 18–21 use the proper API throughout.

5. WordPress vs. Laravel: What Actually Changes

Coming straight from Module 2, a few mental adjustments matter:

  • No MVC structure to lean on — WordPress's organization is theme templates + hooks, not controllers and routes
  • No Eloquent — data access goes through WordPress's own functions (WP_Query, get_posts()) over the schema above
  • No Composer-first culture at the WordPress-core level, though it's increasingly used for custom theme/plugin dependencies (as you'll do starting Week 18)
  • Global functions and globals ($post, $wpdb) are idiomatic here, where Module 2 avoided them deliberately

None of this makes WordPress "worse" than Laravel — it's optimized for a different problem: non-developers managing content through an admin UI, with theme/plugin code providing structure and extension points around that. The next four weeks build fluency in that model specifically.

6. Hands-on Exercise

Hands-on

Set up local WordPress and explore its schema with wp-cli

Get a working install running, then look under the hood at exactly what it stores.

Requirements:

  1. Install WordPress locally (Local, or the manual php -S route) and complete setup, creating an admin account.
  2. Install wp-cli and confirm wp core version works from inside the install directory.
  3. Use wp post create to create 5 posts and 2 pages from the command line.
  4. Use wp db query to run a SELECT against wp_posts confirming your 7 new rows exist, with the correct post_type distinguishing posts from pages.
  5. Add post meta to one post with wp post meta add <id> my_key "my value", then query wp_postmeta directly to see how it's stored.
Hint

Run wp help post create and wp help post meta to see every available flag — wp-cli's built-in help is thorough and faster to check than searching documentation for most day-to-day tasks.

7. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

Structurally, what's the actual difference between a WordPress "post" and a "page"?

Almost nothing at the database level — both live in the same wp_posts table, and a "page" is simply a row where post_type = 'page' instead of 'post'. The practical difference is presentational and organizational convention: posts are chronological/categorized content, pages are standalone, but WordPress's underlying storage treats them as the same kind of thing.

Q2

Why is wp_postmeta described as an Entity-Attribute-Value pattern rather than a normalized table the way Week 6 would design one?

Instead of dedicated typed columns for each piece of metadata, wp_postmeta stores arbitrary key/value pairs as rows — (post_id, meta_key, meta_value) — letting any post carry any number of arbitrary fields without a schema change. This is far more flexible than fixed columns (any plugin can attach its own metadata to any post without altering the table), at the cost of losing type safety and the kind of query performance a properly normalized, purpose-built schema would offer.

Q3

Why does this lesson warn against querying wp_ tables directly in real plugin or theme code?

WordPress's built-in APIs (WP_Query, get_post_meta(), and similar) handle caching, fire the hooks other plugins may depend on, and correctly account for schema details and edge cases that change across WordPress versions. Querying the tables directly bypasses all of that — it might work today, but it can silently break with a core update or conflict with how another plugin expects to interact with the same data.

Q4

What's the WordPress-side equivalent of the separation Laravel enforces between "controllers" and "views"?

There isn't a strict equivalent — WordPress has no built-in MVC layer. The nearest analogous separation is convention-based: themes are responsible for presentation (their template files play a role similar to Blade views), while plugins are responsible for behavior and data (closer to a controller's job), coordinated through hooks rather than routes. It's a looser, more convention-driven boundary than Laravel's explicit framework structure.