Lesson 10: Building Your First Plugin Dashboard

Introduction

In the previous lesson, we created our first WordPress administration menu. Although our plugin now has its own dashboard page, the content is very simple.

Professional plugins do more than display a welcome message. They provide administrators with useful information at a glance, such as plugin status, database health, statistics, and recent activity.

In this lesson, we’ll transform our basic dashboard into a professional-looking administration page by displaying several information cards.

Even though most of the values are placeholders for now, this dashboard establishes the layout we’ll continue enhancing throughout the project.


Learning Objectives

By the end of this lesson, you’ll be able to:

  • Build a professional WordPress dashboard page.
  • Display plugin information.
  • Count database records.
  • Use WordPress dashboard styling.
  • Prepare the interface for future features.

Why Build a Dashboard First?

As our plugin grows, administrators will need quick access to important information.

Instead of searching through multiple pages, they’ll be able to see everything from one central dashboard.

Later, this page will display:

  • Total Auctions
  • Active Auctions
  • Sold Auctions
  • Total Bids
  • Pending Transfers
  • Escrow Transactions
  • Plugin Version
  • Database Status

Today’s lesson lays the foundation for that dashboard.


Step 1 – Open the Admin Class

Open:

admin/class-admin.php

We’ll replace the simple welcome page with a more useful dashboard.


Step 2 – Replace dashboard_page()

Replace the existing dashboard_page() method with the following:

/**
 * Dashboard page.
 */
public function dashboard_page() {

	global $wpdb;

	$table = $wpdb->prefix . 'flipnzee_auctions';

	$total_auctions = (int) $wpdb->get_var(
		"SELECT COUNT(*) FROM {$table}"
	);

	?>

	<div class="wrap">

		<h1>Flipnzee Auctions Dashboard</h1>

		<p>Welcome to the Flipnzee Auctions administration panel.</p>

		<table class="widefat striped">

			<thead>

				<tr>

					<th>Information</th>

					<th>Value</th>

				</tr>

			</thead>

			<tbody>

				<tr>

					<td>Plugin Version</td>

					<td><?php echo esc_html( FLIPNZEE_AUCTION_VERSION ); ?></td>

				</tr>

				<tr>

					<td>Total Auctions</td>

					<td><?php echo esc_html( $total_auctions ); ?></td>

				</tr>

				<tr>

					<td>Database Table</td>

					<td><?php echo esc_html( $table ); ?></td>

				</tr>

				<tr>

					<td>Plugin Status</td>

					<td>Active</td>

				</tr>

			</tbody>

		</table>

	</div>

	<?php
}

Understanding the Code

The dashboard retrieves the total number of auctions using:

$wpdb->get_var()

Unlike get_row() or get_results(), this method returns only a single value.

In our case:

SELECT COUNT(*)

returns the number of auction records stored in the database.


Why Use COUNT(*)?

Imagine there are:

Auction 1

Auction 2

Auction 3

Instead of loading all three records, MySQL simply returns:

3

This is much faster and more efficient.


Step 3 – Upload the Plugin

Create a new ZIP.

Upload it.

Replace the existing plugin.

Refresh the Dashboard.


What You Should See

Your dashboard should now display something similar to:

Flipnzee Auctions Dashboard

Plugin Version      1.0.0

Total Auctions      0

Database Table      wp_flipnzee_auctions

Plugin Status       Active

Because we haven’t created any auctions yet, the total remains zero.

That’s exactly what we expect.


Lesson Summary

In this lesson, we transformed our simple administration page into a functional plugin dashboard.

Although the statistics are currently minimal, we’ve established a reusable layout that will gradually expand as more features are added.


Key Takeaways

  • ✓ Use $wpdb->get_var() to retrieve a single value.
  • COUNT(*) efficiently counts database records.
  • ✓ Dashboards provide administrators with useful information.
  • ✓ Build the user interface gradually.
  • ✓ Reuse WordPress styling whenever possible.

Common Mistakes

  • Loading unnecessary data when only a count is required.
  • Forgetting to escape output.
  • Mixing business logic with HTML.
  • Hardcoding plugin information.

Git Commands Used

git add .

git commit -m "Lesson 10: Build plugin dashboard"

git push

Project Status

✅ Development environment

✅ Plugin skeleton

✅ Plugin installation

✅ Plugin lifecycle

✅ .gitignore

✅ Data architecture

✅ Database table

✅ Auction Manager

✅ Retrieve auction records

✅ WordPress admin menu

✅ Plugin dashboard

⬜ Create first auction

⬜ Auction listing page

⬜ Bid engine

⬜ Escrow workflow

⬜ Website transfer

⬜ Notifications

⬜ Version 1.0

Project Evolution

Earlier in the series, our focus was on backend architecture. Now we’re beginning to expose that functionality through a professional administration interface.

As additional features are developed, this dashboard will evolve into the central control panel for the entire auction marketplace, giving administrators quick access to auctions, bids, transfers, and transaction status.

The GitHub repository will always contain the latest implementation, while these lessons explain the reasoning behind each architectural decision.


Developer’s Notebook

A dashboard is often the first screen administrators see when using a plugin. A clean, informative dashboard creates confidence and reduces the time needed to find important information. Even simple statistics such as plugin version, total records, and database status can be valuable during development and troubleshooting.


Looking Ahead

In Lesson 11, we’ll create our first Add New Auction page. Instead of working directly with database methods, administrators will begin creating auctions through a WordPress form, bringing the plugin one step closer to becoming a fully functional marketplace.

Lesson 9: Creating Your First WordPress Admin Menu

Introduction

In the previous lessons, we’ve built a solid backend for our plugin.

We have:

  • A professional plugin structure.
  • A custom database table.
  • An Auction Manager class.
  • Methods for creating and retrieving auction records.

However, everything is still happening behind the scenes.

Professional WordPress plugins provide an administration interface where site owners can manage data without editing code. In this lesson, we’ll create our first administration menu for Flipnzee Auctions.

Although the page will initially contain only placeholder content, it establishes the foundation upon which we’ll build auction management, bidding, transaction workflows, and plugin settings.


Learning Objectives

By the end of this lesson, you’ll be able to:

  • Understand how WordPress admin menus work.
  • Create your first top-level administration menu.
  • Display a custom administration page.
  • Learn about the admin_menu hook.
  • Organize admin-related code into its own class.

Why Use an Admin Class?

As our plugin grows, we’ll eventually have multiple administration pages:

  • Dashboard
  • Auctions
  • Bids
  • Transactions
  • Settings
  • Reports

Rather than placing all this code inside flipnzee-auctions.php, we’ll keep everything related to the administration area inside its own class.

This makes the plugin easier to maintain.


Step 1 – Create the Admin Folder

Inside your plugin folder, create a new directory:

admin

Your structure now becomes:

flipnzee-auctions/
│
├── admin/
├── includes/
├── flipnzee-auctions.php

Step 2 – Create the Admin Class

Inside the admin folder create:

class-admin.php

Step 3 – Add the Admin Class

Copy the following code into the file.

<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Flipnzee_Auction_Admin {

	public function __construct() {

		add_action( 'admin_menu', array( $this, 'register_menu' ) );
	}

	/**
	 * Register the admin menu.
	 */
	public function register_menu() {

		add_menu_page(
			'Flipnzee Auctions',
			'Flipnzee Auctions',
			'manage_options',
			'flipnzee-auctions',
			array( $this, 'dashboard_page' ),
			'dashicons-hammer',
			26
		);
	}

	/**
	 * Dashboard page.
	 */
	public function dashboard_page() {

		echo '<div class="wrap">';
		echo '<h1>Flipnzee Auctions</h1>';
		echo '<p>Welcome to the Flipnzee Auctions plugin.</p>';
		echo '<p>This dashboard will gradually grow throughout this tutorial series.</p>';
		echo '</div>';
	}
}

new Flipnzee_Auction_Admin();

Understanding add_menu_page()

The function receives several arguments.

add_menu_page(
    Page Title,
    Menu Title,
    Capability,
    Menu Slug,
    Callback Function,
    Icon,
    Position
);

Let’s briefly look at each one.


Page Title

Appears at the top of the browser window.


Menu Title

The text displayed in the WordPress sidebar.


Capability

manage_options

Only administrators can access the page.


Menu Slug

A unique identifier used internally by WordPress.


Callback Function

The function responsible for displaying the page.


Icon

We’re using:

dashicons-hammer

WordPress includes hundreds of built-in Dashicons that can be used for plugin menus.


Position

The menu position in the WordPress sidebar.


Step 4 – Load the Admin Class

Open:

flipnzee-auctions.php

Add the following code immediately after loading the Auction Manager.

/**
 * Load Admin Class
 */
if ( file_exists( FLIPNZEE_AUCTION_PATH . 'admin/class-admin.php' ) ) {
	require_once FLIPNZEE_AUCTION_PATH . 'admin/class-admin.php';
}

Step 5 – Upload the Updated Plugin

Delete the previous ZIP.

Create a new ZIP.

Upload it to WordPress.

Activate the plugin if necessary.


Step 6 – Visit the Dashboard

Open the WordPress administration area.

You should now see a new menu item:

Flipnzee Auctions

Clicking it should display your first plugin dashboard.

Congratulations! Your plugin now has its own administration interface.


Lesson Summary

In this lesson, we created the first administration page for Flipnzee Auctions. Rather than mixing administration code with plugin initialization, we introduced a dedicated Admin class responsible for registering the plugin’s menu and displaying its dashboard.

Although the page currently contains only simple placeholder content, it provides the framework for all future administration features.


Key Takeaways

  • ✓ Use admin_menu to register administration pages.
  • ✓ Keep administration code in a separate class.
  • ✓ Use add_menu_page() to create top-level menus.
  • ✓ Build the user interface gradually.
  • ✓ Maintain a clean project structure.

Common Mistakes

  • Putting administration code inside the main plugin file.
  • Forgetting to instantiate the Admin class.
  • Using an incorrect capability.
  • Loading the Admin class after trying to use it.

Git Commands Used

git add .

git commit -m "Lesson 9: Create WordPress admin menu"

git push

Project Status

✅ Development environment

✅ Plugin skeleton

✅ Plugin installation

✅ Plugin lifecycle

✅ .gitignore

✅ Data architecture

✅ Database table

✅ Auction Manager

✅ Retrieve auction records

✅ WordPress admin menu

⬜ Dashboard widgets

⬜ Create auctions

⬜ Bid engine

⬜ Escrow workflow

⬜ Website transfer

⬜ Notifications

⬜ Version 1.0

Project Evolution

Earlier in the series, our focus was entirely on the backend architecture. Now that the foundation is in place, we’re beginning to build the user interface. This reflects a common professional development workflow: first establish the application’s core logic, then expose that functionality through a clean and intuitive interface.

As always, the GitHub repository contains the latest implementation, while these lessons explain the reasoning behind each architectural decision.


Developer’s Notebook

One of the strengths of WordPress is its consistent administration interface. Rather than inventing our own dashboard, we integrate seamlessly with the existing WordPress admin area. Users already understand how the Dashboard works, so following WordPress conventions makes the plugin feel familiar and professional from the very beginning.


Looking Ahead

In Lesson 10, we’ll enhance the dashboard by displaying useful information such as the total number of auctions, database status, plugin version, and recent activity. This will transform our placeholder page into the first functional administration dashboard for Flipnzee Auctions.

Lesson 8: Retrieving Auction Records from the Database

Introduction

In the previous lesson, we created our first Auction Manager class and added the create_auction() method. Although the method is capable of inserting auction records into the database, we haven’t used it yet. As a result, our wp_flipnzee_auctions table is still empty.

Before building forms or administration pages, it’s important to learn how to retrieve information from the database.

Professional software is built in layers. First we create the database, then we build classes that communicate with it, and only afterwards do we create user interfaces.

In this lesson, we’ll expand the Auction Manager by adding methods that retrieve auction records from the database.


Learning Objectives

By the end of this lesson, you’ll be able to:

  • Retrieve a single auction by its ID.
  • Retrieve all auctions.
  • Understand WordPress’s $wpdb->get_row() method.
  • Understand WordPress’s $wpdb->get_results() method.
  • Continue separating database logic from presentation.

Why Read Before Writing?

Most applications spend far more time reading data than writing it.

Think about Flipnzee.

Visitors browse listings.

Buyers search auctions.

Search engines index pages.

Administrators review sales.

All of these activities require reading data from the database.

Learning to retrieve records is therefore just as important as learning to create them.


Step 1 – Open the Auction Manager

Open:

includes/class-auction-manager.php

We’ll keep our existing create_auction() method.

Now we’ll extend the class.


Step 2 – Add a Method to Retrieve One Auction

Add the following method below create_auction().

/**
 * Retrieve one auction by ID.
 *
 * @param int $auction_id Auction ID.
 *
 * @return object|null
 */
public static function get_auction( $auction_id ) {

	global $wpdb;

	$table = $wpdb->prefix . 'flipnzee_auctions';

	return $wpdb->get_row(
		$wpdb->prepare(
			"SELECT * FROM {$table} WHERE id = %d",
			$auction_id
		)
	);
}

Understanding get_row()

This method retrieves a single record.

If the auction exists:

Auction #15

it returns one object.

If the auction doesn’t exist:

Auction #999

it returns:

null

Why Use $wpdb->prepare()?

Never insert user input directly into SQL queries.

Instead of writing:

SELECT * FROM table WHERE id = $auction_id

we write:

$wpdb->prepare(...)

WordPress safely prepares the query before sending it to MySQL.

This helps protect your plugin from SQL injection attacks.


Step 3 – Retrieve All Auctions

Now add another method.

/**
 * Retrieve all auctions.
 *
 * @return array
 */
public static function get_all_auctions() {

	global $wpdb;

	$table = $wpdb->prefix . 'flipnzee_auctions';

	return $wpdb->get_results(
		"SELECT * FROM {$table} ORDER BY created_at DESC"
	);
}

Understanding get_results()

Unlike get_row(),

get_results()

returns multiple rows.

If the database contains:

Auction 1

Auction 2

Auction 3

you receive an array containing three objects.

If there are no auctions yet:

you receive an empty array.


Why Aren’t We Showing Results Yet?

You might wonder:

Why don’t we display these auctions immediately?

Because our plugin still doesn’t have an administration page.

For now, we’re building the backend.

Soon, the administration interface will simply call these methods.


Our Growing Auction Manager

Our class now contains:

create_auction()

get_auction()

get_all_auctions()

Soon we’ll add:

update_auction()

delete_auction()

close_auction()

Notice how everything related to auctions stays inside one class.


Lesson Summary

In this lesson, we expanded the Auction Manager by adding methods to retrieve auction records from the database.

Although our table currently contains zero records, our plugin is now capable of reading both individual auctions and complete auction lists.

This prepares us for building the administration interface in future lessons.


Key Takeaways

  • ✓ Use get_row() when retrieving one record.
  • ✓ Use get_results() when retrieving multiple records.
  • ✓ Always use $wpdb->prepare() with user input.
  • ✓ Keep database operations inside dedicated classes.
  • ✓ Build the backend before the user interface.

Common Mistakes

  • Writing raw SQL with user input.
  • Mixing SQL with HTML.
  • Forgetting to use $wpdb->prepare().
  • Returning multiple rows when only one is expected.

Git Commands Used

git add .

git commit -m "Lesson 8: Retrieve auction records"

git push

Project Status

✅ Development environment

✅ Plugin skeleton

✅ Plugin installation

✅ Plugin lifecycle

✅ .gitignore

✅ Data architecture

✅ Database table

✅ Auction Manager

✅ Retrieve auction records

⬜ Create admin menu

⬜ Create first auction

⬜ Bid engine

⬜ Escrow workflow

⬜ Website transfer

⬜ Notifications

⬜ Version 1.0

Project Evolution

Throughout this project, we’ve been refining the architecture as new requirements become clearer.

One important decision was separating listing management from auction management. Website information remains the responsibility of Flipnzee Analytics, while Flipnzee Auctions focuses on auction logic and transaction workflows.

This lesson continues that philosophy by keeping all database operations inside the Auction Manager rather than scattering SQL queries throughout the plugin.

As always, the GitHub repository contains the latest implementation, while these lessons document the reasoning behind each architectural decision.


Developer’s Notebook

One of the easiest ways to identify well-structured software is by looking at how it accesses the database. Instead of allowing every part of the application to execute SQL queries, professional developers usually centralize database operations inside dedicated classes. This makes the code easier to test, easier to debug, and much simpler to extend. As Flipnzee Auctions grows to include bidding, escrow tracking, and website transfers, this design will allow us to add new functionality without creating unnecessary complexity.


Looking Ahead

In Lesson 9, we’ll create our first WordPress Administration Menu for Flipnzee Auctions. Instead of interacting with the database directly, administrators will begin managing auctions through the WordPress Dashboard. This marks the transition from backend infrastructure to a real user interface.

Lesson 7: Building the Auction Manager – Inserting and Managing Auction Records

Introduction

In the previous lesson, we created our first custom database table using WordPress’s dbDelta() function. Although the table exists, it is currently empty. Our plugin has a place to store auction data, but it doesn’t yet know how to create, retrieve, update, or delete auction records.

This is where an Auction Manager comes in.

Rather than scattering database queries throughout our plugin, we’ll create a dedicated class responsible for interacting with the auction table. This approach keeps our code organized, easier to maintain, and easier to extend as new features are added.

In this lesson, we’ll build the first version of the Auction Manager and learn how to insert our first auction into the database.


Learning Objectives

By the end of this lesson, you’ll be able to:

  • Understand the purpose of an Auction Manager.
  • Create a dedicated class for database operations.
  • Insert records into the database using WordPress’s $wpdb.
  • Understand how $wpdb->insert() works.
  • Keep database logic separate from the rest of your plugin.

Why Create an Auction Manager?

Imagine writing database queries everywhere throughout the plugin.

Soon you’d have SQL scattered across dozens of files.

Instead, we’ll centralize all auction-related database operations inside one class.

Our future Auction Manager will eventually handle:

  • Creating auctions
  • Updating auctions
  • Retrieving auctions
  • Deleting auctions
  • Ending auctions
  • Selecting winners
  • Checking auction status

Keeping all these operations together makes the plugin much easier to maintain.


Step 1 – Create a New Class

Inside the includes folder, create:

class-auction-manager.php

Step 2 – Create the Class

Copy the following code into the file:

<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Flipnzee_Auction_Manager {

	/**
	 * Create a new auction.
	 *
	 * @param int   $listing_id    The ID of the listing from Flipnzee Analytics.
	 * @param float $start_price   Starting auction price.
	 * @param float $reserve_price Reserve price.
	 * @param float $buy_now_price Buy Now price.
	 *
	 * @return int|false Auction ID on success, false on failure.
	 */
	public static function create_auction( $listing_id, $start_price, $reserve_price, $buy_now_price ) {

		global $wpdb;

		$table = $wpdb->prefix . 'flipnzee_auctions';

		$result = $wpdb->insert(
			$table,
			array(
				'listing_id'    => $listing_id,
				'status'        => 'draft',
				'start_price'   => $start_price,
				'reserve_price' => $reserve_price,
				'buy_now_price' => $buy_now_price,
				'current_bid'   => 0,
			),
			array(
				'%d',
				'%s',
				'%f',
				'%f',
				'%f',
				'%f',
			)
		);

		if ( false === $result ) {
			return false;
		}

		return $wpdb->insert_id;
	}
}

Understanding $wpdb->insert()

Instead of writing raw SQL, WordPress provides helper methods.

Our code:

$wpdb->insert(...)

is equivalent to writing an SQL INSERT statement, but it’s safer and easier to read.

WordPress also prepares the query correctly, reducing the risk of SQL injection.


Understanding the Parameters

The method receives three arguments:

1. Table Name

$table

This tells WordPress where to insert the data.


2. Data Array

array(
    'listing_id' => $listing_id,
    ...
)

Each array key corresponds to a database column.


3. Format Array

array(
    '%d',
    '%s',
    '%f'
)

This tells WordPress what type of data is being inserted.

Examples include:

  • %d Integer
  • %s String
  • %f Decimal

Providing formats makes the query safer and more reliable.


Step 3 – Load the Auction Manager

Open:

flipnzee-auctions.php

Below the database class, add:

/**
 * Load Auction Manager
 */
if ( file_exists( FLIPNZEE_AUCTION_PATH . 'includes/class-auction-manager.php' ) ) {
	require_once FLIPNZEE_AUCTION_PATH . 'includes/class-auction-manager.php';
}

Step 4 – Testing the Class

For now, we’re only building the class.

We won’t yet create auctions from the WordPress dashboard.

That will happen in a later lesson when we build the administration interface.

At this stage, our goal is simply to prepare the plugin architecture.


Why We Aren’t Creating Auctions Yet

Many beginners expect to see forms immediately.

Professional software development works differently.

First we build:

  • Database
  • Database classes
  • Business logic

Only then do we build:

  • Admin pages
  • Forms
  • Buttons
  • User interfaces

This layered approach produces cleaner software.


Looking Ahead

Eventually our Auction Manager will grow to include methods such as:

create_auction()

get_auction()

update_auction()

delete_auction()

close_auction()

select_winner()

Today’s version is intentionally simple.

We’ll expand it gradually throughout the series.


Lesson Summary

In this lesson, we created the first version of the Auction Manager class. Rather than placing database queries throughout the plugin, we introduced a dedicated class responsible for creating auction records.

Although the class currently performs only one operation, it establishes the architectural pattern we’ll use for all future auction-related functionality.


Key Takeaways

  • ✓ Keep database logic inside dedicated classes.
  • ✓ Use $wpdb->insert() instead of writing raw SQL.
  • ✓ Store one responsibility in one class.
  • ✓ Load new classes through the main plugin file.
  • ✓ Build the backend before building the user interface.

Common Mistakes

  • Writing SQL throughout multiple files.
  • Forgetting the format array in $wpdb->insert().
  • Mixing HTML and database code together.
  • Trying to build the admin interface before the backend is ready.

Git Commands Used

git add .

git commit -m "Lesson 7: Create Auction Manager"

git push

Project Status

✅ Development environment

✅ Plugin skeleton

✅ Plugin installation

✅ Plugin lifecycle

✅ .gitignore

✅ Data architecture

✅ Database table

✅ Auction Manager

⬜ Admin interface

⬜ Create auctions

⬜ Bid engine

⬜ Escrow workflow

⬜ Website transfer

⬜ Notifications

⬜ Version 1.0

Project Evolution

If you’ve been following the series from the beginning, you may notice that our architecture continues to evolve.

Originally, we planned for Flipnzee Auctions to manage both website listings and auctions. As the project matured, we recognized that Flipnzee Analytics already provides listing management, analytics, and verification.

Rather than duplicating those responsibilities, the Auction Manager focuses exclusively on auction operations. This separation of concerns keeps the codebase modular and allows both plugins to evolve independently while working together through the listing_id relationship.

The GitHub repository always contains the latest implementation, while these lessons document the reasoning behind each architectural decision.


Developer’s Notebook

One of the hallmarks of professional software is that it separates business logic from presentation. Users interact with forms and buttons, but those interfaces should simply call well-designed classes that perform the actual work. By introducing an Auction Manager early, we’re laying the foundation for a codebase that will remain organized even as the plugin grows to include bidding, escrow workflows, notifications, and website transfers.


Looking Ahead

In Lesson 8, we’ll use the Auction Manager to retrieve auction records from the database and display them inside WordPress. This will be our first step toward building a working administration interface and bringing real auction data onto the screen.

How to Verify That Your WordPress Plugin Created a Database Table (Hostinger phpMyAdmin & Other Methods)

When developing a WordPress plugin, one of the most satisfying moments is seeing your first custom database table appear.

In our Flipnzee Auctions series, we created the wp_flipnzee_auctions table using WordPress’s dbDelta() function.

But how do we know the table was actually created?

There are several ways to verify this. In this article, I’ll show the method I used while developing Flipnzee Auctions on Hostinger, along with several other approaches that work on different hosting providers.


Why Verify the Database?

Suppose you activate your plugin and nothing appears to happen.

Did the activation hook run?

Was the SQL correct?

Did dbDelta() create the table?

Rather than guessing, it’s always a good idea to verify the database.


Method 1 – Using Hostinger phpMyAdmin (Recommended)

Since Flipnzee.com is hosted on Hostinger, this is the method I personally used.

Step 1

Log in to your Hostinger hPanel.


Step 2

Open:

Websites → Manage

for your WordPress website.


Step 3

Open:

Databases → phpMyAdmin


Step 4

Select your WordPress database.

You’ll see all WordPress tables listed in the left sidebar.


Step 5

Scroll through the list.

After activating the Flipnzee Auctions plugin, I found the newly created table:

wp_flipnzee_auctions

This confirmed that the activation hook executed successfully and that WordPress created the database table.


My Development Screenshot

The following screenshot shows the actual database used while developing the Flipnzee Auctions plugin.

Notice the presence of the wp_flipnzee_auctions table among the standard WordPress tables.


Method 2 – phpMyAdmin on Other Hosting Providers

Most shared hosting providers offer phpMyAdmin.

For example:

  • cPanel Hosting
  • Bluehost
  • SiteGround
  • DreamHost
  • A2 Hosting
  • Namecheap Hosting

The process is almost identical:

  1. Open phpMyAdmin.
  2. Select your database.
  3. Look for your custom table.

Method 3 – MySQL Workbench

Many professional developers use:

  • MySQL Workbench

Connect using your database credentials.

Refresh the database.

Your custom table should appear automatically.


Method 4 – Adminer

Adminer is a lightweight alternative to phpMyAdmin.

Many developers prefer it because it’s fast and easy to use.

Simply connect to your database and check whether your table exists.


Method 5 – DBeaver

Another popular database tool is DBeaver.

It’s free and supports multiple database systems.

After connecting to your WordPress database, simply refresh the tables list.


Method 6 – Create an Admin Dashboard (Future Lesson)

As our plugin evolves, we won’t need to open phpMyAdmin every time.

Instead, we’ll build an administration page inside Flipnzee Auctions that displays information such as:

Plugin Status

✓ Plugin Active

✓ Database Connected

✓ Auction Table Exists

Version 1.0.0

This provides a much friendlier experience for plugin users.

We’ll build this later in the series.


Which Method Should Beginners Use?

If you’re just getting started with WordPress plugin development, phpMyAdmin is usually the easiest place to begin because it lets you see exactly what’s happening behind the scenes.

As you become more experienced, you’ll probably rely more on dedicated database tools or build your own plugin diagnostics.


Lesson Learned

Creating a database table is only half the job.

Professional developers always verify that the database matches what the code intended to create.

Developing the habit of checking your database after major schema changes can save hours of debugging later.


Final Thoughts

One of the goals of the Flipnzee Auctions project is not just to build a marketplace plugin, but to understand what happens behind the scenes as WordPress plugins interact with the database.

Learning to inspect your database is an important step toward becoming a confident WordPress developer.

As we continue this series, we’ll gradually move from simply creating tables to inserting records, updating them, retrieving data, and eventually powering a complete website marketplace.

Lesson 6: Creating Your First Database Table with dbDelta()

Introduction

In the previous lesson, we made one of the most important architectural decisions for the Flipnzee ecosystem.

Rather than building one massive plugin that manages everything, we decided to split responsibilities between two plugins:

  • Flipnzee Analytics manages website listings, verification, Google Analytics, Search Console data, and marketplace insights.
  • Flipnzee Auctions manages auctions, bidding, transactions, and the website transfer process.

This separation of responsibilities keeps both plugins modular, easier to maintain, and easier to extend in the future.

Now it’s time to create our first custom database table.

WordPress provides a special function called dbDelta() that safely creates or updates database tables. Instead of executing raw SQL directly, dbDelta() compares your desired table structure with the existing database and applies only the necessary changes.

In this lesson, we’ll use dbDelta() to create the first table for the Flipnzee Auctions plugin.


Learning Objectives

By the end of this lesson, you’ll be able to:

  • Understand why WordPress provides the dbDelta() function.
  • Create a custom database table during plugin activation.
  • Use the $wpdb object correctly.
  • Understand the importance of $wpdb->prefix.
  • Design a database table that complements another plugin instead of duplicating data.

Why Are We Creating a Custom Table?

Earlier in this series, we decided that auctions are transactional data, not content.

A website listing already exists in Flipnzee Analytics.

The auction plugin only needs to know:

  • Which listing is being auctioned.
  • The auction settings.
  • The current bid.
  • The auction status.
  • Who won the auction.

This keeps each plugin focused on its own responsibility.


Our Plugin Architecture

Our project now looks like this:

Flipnzee Analytics
--------------------------
Website Listings
Google Analytics
Search Console
Traffic
Revenue
Verification

          │
          ▼

Flipnzee Auctions
--------------------------
Auction
Bids
Escrow Workflow
Transfers
Watchlists
Notifications

Notice that the Auctions plugin does not store website information.

Instead, it references an existing listing.


Why Not Duplicate Website Data?

Suppose we copied the following into the auction table:

  • Website title
  • Monthly traffic
  • Revenue
  • Domain age

Now imagine the seller updates the listing.

Which copy should the buyer trust?

Instead of maintaining two copies of the same information, our auction simply stores a reference to the listing managed by Flipnzee Analytics.

This is a common software engineering principle known as avoiding duplication.


Step 1 – Create the Database Class

Inside the includes folder, create a new file:

class-database.php

Step 2 – Add the Database Class

<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Flipnzee_Auction_Database {

	public static function create_tables() {

		global $wpdb;

		$table_name = $wpdb->prefix . 'flipnzee_auctions';

		$charset_collate = $wpdb->get_charset_collate();

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';

		$sql = "CREATE TABLE {$table_name} (

			id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,

			listing_id BIGINT UNSIGNED NOT NULL,

			status VARCHAR(30) DEFAULT 'draft',

			start_price DECIMAL(12,2) DEFAULT 0,

			reserve_price DECIMAL(12,2) DEFAULT 0,

			buy_now_price DECIMAL(12,2) DEFAULT 0,

			current_bid DECIMAL(12,2) DEFAULT 0,

			auction_start DATETIME NULL,

			auction_end DATETIME NULL,

			winner_user_id BIGINT UNSIGNED DEFAULT NULL,

			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,

			updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

			PRIMARY KEY (id),

			KEY listing_id (listing_id),

			KEY status (status)

		) {$charset_collate};";

		dbDelta( $sql );

	}
}

Understanding Each Field

id

The unique identifier for each auction.


listing_id

This is the most important field in our table.

Instead of storing website information again, we simply reference the existing listing managed by Flipnzee Analytics.

One listing may eventually have multiple auctions over its lifetime without duplicating data.


status

Examples include:

  • draft
  • scheduled
  • active
  • ended
  • sold
  • cancelled

start_price

The opening bid.


reserve_price

The seller’s minimum acceptable price.


buy_now_price

Allows a buyer to immediately purchase the website without waiting for the auction to end.


current_bid

Stores the highest bid.

Although individual bids will later have their own database table, storing the current highest bid here allows auction listings to load much faster.


auction_start

The date and time bidding begins.


auction_end

The date and time bidding closes.


winner_user_id

After the auction ends, this field stores the winning bidder.


created_at and updated_at

These timestamps help us monitor when auction records are created and modified.


Step 3 – Load the Database Class

Open:

flipnzee-auctions.php

Add:

require_once FLIPNZEE_AUCTION_PATH . 'includes/class-database.php';

Step 4 – Update the Activation Hook

Replace your activation function with:

function flipnzee_auction_activate()
{
	Flipnzee_Auction_Database::create_tables();

	flush_rewrite_rules();
}

Now, whenever the plugin is activated, WordPress checks whether the auction table exists.

If it doesn’t, dbDelta() creates it automatically.


Step 5 – Test Your Plugin

Deactivate the plugin.

Activate it again.

If everything has been configured correctly, the database table will be created without displaying any errors.


Step 6 – Verify the Table

Open your preferred database manager, such as:

  • phpMyAdmin
  • Adminer
  • MySQL Workbench

You should now see:

wp_flipnzee_auctions

If your WordPress installation uses a different table prefix, the table name will begin with that prefix instead of wp_.


Why We’re Keeping This Table Simple

You might notice that this table doesn’t include:

  • Bid history
  • Payments
  • Escrow transactions
  • Website transfers

That’s intentional.

Each of these responsibilities will receive its own dedicated database table in future lessons.

Keeping each table focused on a single responsibility makes the database easier to maintain and allows the plugin to scale as more features are added.


Lesson Summary

In this lesson, we created our first custom database table using WordPress’s dbDelta() function. Instead of duplicating website information, we designed the table to reference listings managed by Flipnzee Analytics using the listing_id field.

This modular architecture allows Flipnzee Analytics to remain the source of truth for website information while Flipnzee Auctions focuses entirely on managing auctions and transactions.


Key Takeaways

  • ✓ Use dbDelta() when creating WordPress database tables.
  • ✓ Always use $wpdb->prefix.
  • ✓ Avoid duplicating data between plugins.
  • ✓ Design each database table with a single responsibility.
  • ✓ Modular plugins are easier to maintain and extend.

Common Mistakes

  • Hardcoding the wp_ table prefix.
  • Forgetting to load upgrade.php.
  • Storing the same information in multiple tables.
  • Mixing listing data with auction data.
  • Forgetting to reactivate the plugin after updating the activation hook.

Git Commands Used

git add .

git commit -m "Lesson 6: Create auction database table"

git push

Project Evolution

If you’ve been following this series from Lesson 0, you may notice that the architecture of Flipnzee Auctions has evolved as the project has grown.

Originally, the plugin was planned as a standalone website auction platform responsible for both listings and auctions.

As development progressed, we realized that Flipnzee Analytics already provides website listings, verification, Google Analytics integration, and Search Console data. Rather than duplicating those responsibilities, we decided to keep Flipnzee Auctions focused on auctions and transaction management.

This architectural change brings several advantages:

  • It avoids storing the same data in multiple places.
  • It keeps each plugin focused on a single responsibility.
  • It makes both plugins easier to maintain.
  • It allows future improvements to Flipnzee Analytics to benefit the auction system automatically.

You’ll notice similar design refinements throughout this series. That’s intentional. Real-world software development is an iterative process, and one of the goals of this series is to demonstrate how professional software evolves over time rather than presenting it as if it were designed perfectly from the beginning.

The GitHub repository will always contain the latest implementation, while these lessons document the reasoning behind each important decision.

Project Status

✅ Development environment

✅ Plugin skeleton

✅ Plugin installation

✅ Plugin lifecycle

✅ .gitignore

✅ Data architecture

✅ First database table

⬜ Auction Manager

⬜ Bid engine

⬜ Escrow workflow

⬜ Website transfer workflow

⬜ Notifications

⬜ Version 1.0

Developer’s Notebook

One of the most important principles in software engineering is Separation of Concerns. Each component of a system should have a clearly defined responsibility.

In the Flipnzee ecosystem, Flipnzee Analytics is responsible for collecting and presenting website information, while Flipnzee Auctions is responsible for managing the buying and selling process. By keeping these responsibilities separate, we avoid duplication, reduce complexity, and make both plugins easier to maintain as they evolve.


Looking Ahead

In Lesson 7, we’ll create the Auction Manager class and learn how to insert, retrieve, update, and delete auction records using WordPress’s $wpdb methods. This will be the first time our plugin stores and retrieves real auction data, moving us from database design into application development.

Lesson 5: Designing the Data Model – Custom Post Types, Custom Tables, or Both?


Introduction

Up to this point, we’ve built a solid foundation for the Flipnzee Auctions plugin. Our plugin installs correctly, follows good development practices, and is managed with Git.

Now it’s time to ask one of the most important questions in WordPress plugin development:

Where should we store our data?

WordPress offers several ways to store information, and choosing the right one can significantly affect your plugin’s performance, scalability, and maintainability.

For Flipnzee Auctions, we’ll eventually need to store:

  • Website listings
  • Auctions
  • Bids
  • Escrow transactions
  • Payment records
  • Website transfer progress
  • User watchlists
  • Notifications

Should all of these be stored as WordPress posts? Should we create our own database tables? Or is a hybrid approach the best solution?

In this lesson, we’ll explore the strengths and limitations of each approach before making our architectural decision.


Learning Objectives

By the end of this lesson, you’ll understand:

  • How WordPress stores content.
  • What Custom Post Types (CPTs) are.
  • What custom database tables are.
  • The advantages and disadvantages of each approach.
  • Why many professional plugins combine both.
  • Why Flipnzee Auctions will use a hybrid architecture.

Option 1 – Using Custom Post Types

Many plugins use Custom Post Types to store structured content.

For example:

Post
Page
Product
Event
Property
Listing

Each item is stored in the standard WordPress posts table, while additional information is stored as post meta.

Advantages include:

  • Built-in WordPress admin interface.
  • Revisions.
  • Categories and tags.
  • SEO compatibility.
  • REST API support.
  • Familiar editing experience.

For website listings, this is often an excellent choice.


Option 2 – Using Custom Database Tables

Some plugins create their own tables.

Examples include:

wp_flipnzee_auctions
wp_flipnzee_bids
wp_flipnzee_payments

Advantages include:

  • Faster queries for large datasets.
  • Better indexing.
  • Greater flexibility.
  • Easier reporting.
  • Better performance when storing transactional data.

This approach is commonly used by complex plugins such as e-commerce systems, booking systems, and learning management systems.


Why Not Store Everything as Posts?

Imagine an auction with:

  • 5,000 bids
  • 200 auctions
  • Thousands of payment records

Storing all of this in wp_posts and wp_postmeta would make queries increasingly complex and potentially slower as the site grows.

Transactional data is often better suited to dedicated database tables.


Why Not Store Everything in Custom Tables?

Custom tables are powerful, but they don’t automatically provide:

  • The WordPress editor.
  • Revisions.
  • Built-in REST support.
  • Categories and tags.
  • Media integration.

You would need to build many of these features yourself.


The Hybrid Approach

Many large WordPress plugins use a combination of both methods.

For Flipnzee Auctions, that’s exactly what we’ll do.

Flipnzee Analytics (Existing Plugin)

This plugin already manages website listings and verified analytics.

It provides:

  • Website listing
  • Google Analytics integration
  • Search Console integration
  • Verified metrics

Those listings can continue using WordPress’s strengths.


Flipnzee Auctions (New Plugin)

Our plugin will focus on transactional data such as:

  • Auction settings
  • Bid history
  • Watchlists
  • Escrow records
  • Payment tracking
  • Website transfer workflow

These are excellent candidates for custom database tables.


Our Planned Architecture

Flipnzee Analytics
-------------------
Website Listings
Traffic
Revenue
Verification

        │
        ▼

Flipnzee Auctions
-------------------
Auction
Bids
Escrow
Payments
Transfers
Watchlists

Each plugin has a clear responsibility.

This keeps the overall system modular, easier to maintain, and easier to extend.


Why This Matters

One of the biggest mistakes beginners make is trying to solve every problem with the same tool.

Professional developers choose the storage mechanism that best fits the type of data being stored.

Content and transactions have different requirements.

By recognizing that difference early, we can build a plugin that scales more effectively over time.


Lesson Summary

In this lesson, we explored three approaches to storing data in WordPress:

  • Custom Post Types
  • Custom Database Tables
  • A Hybrid Approach

Rather than choosing one exclusively, we’ve decided that Flipnzee Auctions will complement the existing Flipnzee Analytics plugin by storing transactional data in dedicated database tables while continuing to use WordPress where it makes sense.

This architectural decision will guide the remainder of the project.


Key Takeaways

  • ✓ WordPress provides several ways to store data.
  • ✓ Custom Post Types are ideal for content.
  • ✓ Custom tables are well suited to transactional data.
  • ✓ Many successful plugins use both approaches.
  • ✓ Flipnzee Auctions will use a hybrid architecture.

Common Mistakes

  • Assuming every plugin should use only Custom Post Types.
  • Creating database tables without a clear need.
  • Duplicating data already managed by another plugin.
  • Designing the database before understanding the application’s requirements.

Git Commands Used

git add .

git commit -m "Lesson 5: Design plugin data architecture"

git push

Note: This lesson focuses on architecture and planning. Unless you’ve actually changed files in your repository (such as updating ROADMAP.md or documentation), you don’t need to create a Git commit just because you published the lesson.


Project Status

✅ Development environment

✅ Plugin skeleton

✅ Plugin installation

✅ Plugin lifecycle

✅ .gitignore

✅ Data architecture planned

⬜ Database tables

⬜ Auction model

⬜ Bid engine

⬜ Escrow

⬜ Payment gateways

⬜ Website transfer

⬜ Version 1.0

Developer’s Notebook

Software architecture is about making good decisions before writing code. It’s tempting to start creating tables immediately, but taking time to understand your data leads to cleaner, more maintainable systems. In this series, we’ll build Flipnzee Auctions step by step, ensuring each design decision has a clear purpose rather than simply following convention.


Looking Ahead

In Lesson 6, we’ll create our first custom database table using WordPress’s dbDelta() function. We’ll learn why WordPress provides this function, how it safely creates and updates tables, and how we’ll use it to store auction data as our plugin grows.

Lesson 4: Using .gitignore to Keep Your Repository Clean

In the previous lesson, we learned how WordPress discovers and loads plugins during every request. We also explored the difference between plugin activation and the normal loading process.

While developing Flipnzee Auctions, we created a ZIP file so that we could install and test our plugin in WordPress. That ZIP file served its purpose, but it raised an important question:

Should generated files be stored in Git?

The answer is usually no.

Professional developers use a special file called .gitignore to tell Git which files and folders should never be tracked.

In this lesson, we’ll create our first .gitignore file and learn why keeping a repository clean is an important part of software development.


Learning Objectives

By the end of this lesson, you’ll be able to:

  • Understand the purpose of a .gitignore file.
  • Create a .gitignore file in GitHub Codespaces.
  • Prevent generated ZIP files from being tracked by Git.
  • Understand the difference between source code and generated files.
  • Learn what kinds of files should never be committed.

What Is a .gitignore File?

A .gitignore file tells Git:

“Ignore these files. Don’t show them as changes, and don’t include them in commits.”

It does not delete files.

It simply prevents Git from tracking them.


Why Do We Need One?

Suppose you create:

flipnzee-auctions.zip

This ZIP file is generated from your source code.

It can always be recreated.

If we committed every ZIP file to GitHub, our repository would quickly become cluttered with generated files instead of the source code that actually matters.

Our Git repository should contain the recipe, not the finished product.


Step 1 – Delete the Existing ZIP File

If you still have the ZIP file from Lesson 2, delete it.

GUI Method

In GitHub Codespaces Explorer:

  • Right-click flipnzee-auctions.zip
  • Select Delete

Terminal Method

rm flipnzee-auctions.zip

Verify it has been removed:

ls

The ZIP file should no longer appear.


Step 2 – Create a .gitignore File

Make sure you’re in the repository root.

pwd

Expected output:

/workspaces/flipnzee-auctions

GUI Method

Click New File.

Create:

.gitignore

Terminal Method

touch .gitignore

Open it:

code .gitignore

Step 3 – Add Your First Ignore Rule

Add the following:

# Generated plugin package
flipnzee-auctions.zip

Save the file.

That’s all!

From now on, Git will ignore the generated plugin ZIP.


Step 4 – Let’s Expand It

As our project grows, we’ll generate more temporary files.

Let’s prepare for that now.

Replace the contents of .gitignore with:

# Generated plugin package
flipnzee-auctions.zip

# ZIP files
*.zip

# Log files
*.log

# macOS
.DS_Store

# Windows
Thumbs.db

This is still a very small .gitignore, but it’s a good start.

We’ll continue adding to it throughout the project.


How Git Uses .gitignore

Imagine this repository:

flipnzee-auctions/
│
├── README.md
├── ROADMAP.md
├── LICENSE
├── CHANGELOG.md
├── .gitignore
│
├── flipnzee-auctions/
│
└── flipnzee-auctions.zip

Git sees the ZIP file.

It checks .gitignore.

It finds:

*.zip

Git immediately ignores the ZIP.

The file still exists on your computer.

It simply won’t appear in git status.


Test It Yourself

Create a ZIP again.

zip -r flipnzee-auctions.zip flipnzee-auctions

Now run:

git status

You should not see:

flipnzee-auctions.zip

Git is ignoring it exactly as we intended.


What Should Never Be Committed?

Generally speaking, Git repositories should contain:

✅ Source code

✅ Documentation

✅ Configuration

❌ Generated ZIP files

❌ Temporary files

❌ Cache files

❌ Log files

❌ Operating system metadata


Why This Matters

A clean Git repository has many advantages:

  • Smaller repositories.
  • Faster cloning.
  • Easier code reviews.
  • Fewer accidental commits.
  • More professional project organization.

Keeping only the files that matter makes collaboration much easier.


Lesson Summary

In this lesson, we created our first .gitignore file and learned why professional developers keep generated files out of version control. We also distinguished between source code, which belongs in Git, and generated artifacts, which can always be recreated.

Although .gitignore is a small file, it plays an important role in keeping software projects clean and maintainable.


Key Takeaways

  • .gitignore tells Git which files to ignore.
  • ✓ Ignored files remain on your computer.
  • ✓ Generated ZIP files should not be committed.
  • ✓ Source code belongs in Git.
  • ✓ Build artifacts generally do not.

Common Mistakes

  • Creating .gitignore.txt instead of .gitignore.
  • Adding files to .gitignore after they’ve already been committed.
  • Thinking .gitignore deletes files.
  • Forgetting to save the .gitignore file before testing.

Git Commands Used

touch .gitignore

git status

git add .

git commit -m "Lesson 4: Add .gitignore"

git push

Project Status

✅ Development environment

✅ Plugin skeleton

✅ Plugin installation

✅ Plugin lifecycle

✅ .gitignore

⬜ Database tables

⬜ Auction data model

⬜ Bidding engine

⬜ Escrow

⬜ Payment gateways

⬜ Website transfer

⬜ Version 1.0

Developer’s Notebook

One of the easiest ways to recognize a well-maintained software project is by looking at its Git repository. Professional repositories focus on the source code, documentation, and configuration files that define the project. Temporary files, generated packages, and operating system metadata are intentionally left out. A well-crafted .gitignore file is a small investment that pays dividends throughout the lifetime of a project.


Looking Ahead

In Lesson 5, we’ll take our first step into persistent data by creating the database tables that will power the Flipnzee Auctions plugin. We’ll learn why complex plugins often use custom database tables instead of relying entirely on WordPress post types and post meta, and we’ll create our first table using WordPress’s dbDelta() function.

Lesson 2: Installing and Activating Your First WordPress Plugin

Learning Objectives

By the end of this lesson, you’ll be able to:

  • Package your plugin for WordPress.
  • Install a plugin using the WordPress Dashboard.
  • Activate and deactivate a plugin.
  • Verify that WordPress recognizes your plugin correctly.
  • Understand what happens during plugin activation.
  • Troubleshoot common installation problems.

Why Test Early?

One of the most valuable habits in software development is testing small changes frequently.

Rather than writing thousands of lines of code and testing everything at the end, professional developers test after every significant milestone.

Since we’ve completed the plugin skeleton, now is the perfect time to verify that WordPress recognizes it as a valid plugin.

If everything works at this stage, we can confidently build additional features knowing that our foundation is solid.


Prerequisites

Before continuing, ensure that:

  • GitHub repository has been created.
  • GitHub Codespaces is working.
  • Lesson 1 has been completed.
  • Your changes have been committed and pushed to GitHub.

Step 1: Verify Your Project Structure

Your repository should look similar to this:

flipnzee-auctions/
│
├── README.md
├── ROADMAP.md
├── CHANGELOG.md
├── LICENSE
│
└── flipnzee-auctions/
    │
    ├── flipnzee-auctions.php
    └── includes/
        └── class-loader.php

Notice that only the inner flipnzee-auctions folder will become the WordPress plugin.


Step 2: Create the Plugin ZIP

WordPress installs plugins as ZIP archives.

Open GitHub Codespaces and make sure you’re in the repository root.

pwd

Expected output:

/workspaces/flipnzee-auctions

Now compress only the plugin folder.

Using the terminal:

zip -r flipnzee-auctions.zip flipnzee-auctions

Verify that the ZIP file was created:

ls

You should now see:

CHANGELOG.md
LICENSE
README.md
ROADMAP.md
flipnzee-auctions
flipnzee-auctions.zip

Step 3: Download the ZIP File

In GitHub Codespaces Explorer:

  • Locate flipnzee-auctions.zip
  • Right-click the file.
  • Select Download.

Save it to your computer.


Step 4: Install the Plugin

Log in to your WordPress website.

Navigate to:

Plugins → Add New Plugin

Click:

Upload Plugin

Select:

flipnzee-auctions.zip

Click:

Install Now

If everything is correct, WordPress will display a success message.


Step 5: Activate the Plugin

Click:

Activate Plugin

Navigate to:

Plugins → Installed Plugins

You should now see:

Flipnzee Auctions

Version 1.0.0

By Splendid Digital Solutions

Congratulations! WordPress now recognizes your plugin.

Although it doesn’t yet contain any auction functionality, it has officially become a valid WordPress plugin.


Step 6: Test Activation and Deactivation

Deactivate the plugin.

Activate it again.

Nothing visible should happen—and that’s perfectly normal.

Behind the scenes, WordPress executes the activation and deactivation hooks that we created in Lesson 1.

At the moment they simply refresh the rewrite rules.

In future lessons, those same hooks will create database tables, initialize settings, and perform other setup tasks.


Understanding What Just Happened

When WordPress activated your plugin, it performed several tasks automatically:

  1. Read the plugin header.
  2. Loaded flipnzee-auctions.php.
  3. Checked the ABSPATH security condition.
  4. Defined the plugin constants.
  5. Registered the activation hook.
  6. Loaded the Flipnzee_Auction_Loader class.

Even though your plugin currently has no visible features, WordPress has already built the execution environment that will support everything we add later.


Common Installation Problems

“Plugin could not be activated.”

Usually caused by:

  • PHP syntax errors
  • Missing semicolons
  • Incorrect file names

Plugin Doesn’t Appear

Usually caused by:

  • Plugin header missing
  • Main PHP file inside the wrong folder
  • Incorrect ZIP structure

White Screen

Usually caused by a PHP fatal error.

Enable debugging by adding the following to wp-config.php:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);

Then check:

wp-content/debug.log

Lesson Summary

In this lesson, we packaged our plugin, installed it on a WordPress website, activated it, and confirmed that WordPress recognizes it as a valid plugin. This milestone is important because it proves that our project structure and plugin header are correct before we begin adding more complex functionality.


Key Takeaways

  • WordPress installs plugins from ZIP files.
  • Only the plugin folder—not the entire repository—should be packaged.
  • Testing early helps identify problems before they become difficult to debug.
  • Activation hooks execute every time the plugin is activated.

Common Mistakes

  • Zipping the entire GitHub repository instead of the plugin folder.
  • Uploading the wrong ZIP file.
  • Forgetting to save changes before creating the ZIP.
  • Ignoring PHP errors during activation.

Git Commands Used

zip -r flipnzee-auctions.zip flipnzee-auctions

git add .

git commit -m "Lesson 2: Install plugin in WordPress"

git push

Project Status

✅ Development environment ready

✅ GitHub repository created

✅ Plugin skeleton completed

✅ Plugin installed successfully

⬜ Database installation

⬜ Auction engine

⬜ Bidding system

⬜ Escrow workflow

⬜ Payment gateways

⬜ Website transfer

⬜ Reports

⬜ REST API

⬜ Version 1.0 Release

Developer’s Notebook

Professional developers validate their work continuously. A plugin that installs and activates successfully gives you confidence that the project’s structure is sound. As we continue adding features, we’ll continue testing after each major milestone, making problems easier to isolate and fix.

As the Flipnzee ecosystem grows, this plugin will work alongside other Flipnzee plugins. For example, Flipnzee Analytics is responsible for collecting and displaying verified website metrics, while Flipnzee Auctions focuses on the buying, bidding, payment, and transfer process. Keeping these responsibilities separate makes each plugin easier to maintain and extend.


Looking Ahead

In Lesson 3, we’ll explore the WordPress Plugin Lifecycle. You’ll learn exactly what happens from the moment WordPress loads your plugin until your code is executed. Understanding this lifecycle is essential for writing efficient, secure, and maintainable plugins, and it will prepare us for creating our first database tables in the lessons that follow.


Lesson 3: Understanding the WordPress Plugin Lifecycle

In the previous lesson, we successfully installed and activated the Flipnzee Auctions plugin on our WordPress website. Even though the plugin doesn’t yet display any visible functionality, WordPress already recognizes it as a valid plugin and executes its code whenever appropriate.

But have you ever wondered what actually happens behind the scenes?

How does WordPress discover plugins?

When does it read the plugin header?

Why does the ABSPATH constant already exist?

When are activation hooks executed?

And why do they run only once?

Understanding these questions is one of the biggest differences between simply copying WordPress code and becoming a confident WordPress plugin developer.

In this lesson, we’ll follow the complete journey of our plugin—from the moment a visitor requests a page until our code is executed.


Learning Objectives

By the end of this lesson, you’ll understand:

  • How WordPress discovers installed plugins.
  • What happens during plugin activation.
  • What happens during every page request.
  • Why the ABSPATH security check works.
  • When your plugin code executes.
  • The purpose of activation and deactivation hooks.
  • Why understanding the plugin lifecycle makes debugging easier.

Why This Matters

Many beginners think WordPress “magically” loads plugins.

It doesn’t.

There is a very specific sequence of events.

Once you understand that sequence, many concepts become much easier:

  • Hooks
  • Filters
  • AJAX
  • REST APIs
  • Database creation
  • Custom Post Types
  • Scheduled tasks

Almost every advanced WordPress feature depends on understanding when your code runs.


The Journey Begins

Imagine someone visits:

https://flipnzee.com

The browser sends a request to the web server.

The web server loads WordPress.

WordPress then begins its own startup process.

Eventually it reaches the point where it starts loading plugins.


Step 1 — WordPress Finds Active Plugins

WordPress stores the list of active plugins in its database.

During startup it reads that list.

If your plugin is active:

Flipnzee Auctions

WordPress prepares to load:

flipnzee-auctions/flipnzee-auctions.php

This is why the main plugin file is so important.


Step 2 — WordPress Reads the Plugin Header

Before executing the plugin, WordPress reads the comment block at the top of the file.

/**
 * Plugin Name: Flipnzee Auctions
 * Version: 1.0.0
 * ...
 */

This information is displayed on:

Plugins → Installed Plugins

Notice something interesting.

The plugin header is metadata.

PHP ignores it.

WordPress reads it.


Step 3 — PHP Begins Executing Your File

Now PHP starts reading your code.

The very first executable line is:

if (!defined('ABSPATH')) {
    exit;
}

At this point WordPress has already defined:

ABSPATH

So the condition evaluates to:

false

The plugin continues loading.

If someone tried opening the PHP file directly in a browser:

https://example.com/wp-content/plugins/flipnzee-auctions/flipnzee-auctions.php

ABSPATH wouldn’t exist.

The plugin would immediately exit.

That’s why this simple security check is so important.


Step 4 — Constants Are Defined

Next WordPress executes:

define('FLIPNZEE_AUCTION_VERSION', '1.0.0');

Then

define('FLIPNZEE_AUCTION_PATH', ...);

Then

define('FLIPNZEE_AUCTION_URL', ...);

These constants remain available throughout the rest of the request.


Step 5 — Activation Hooks Are Registered

WordPress encounters:

register_activation_hook(...)

Notice the wording carefully.

We are registering the activation hook.

We are not executing it.

This often confuses beginners.

WordPress simply remembers:

“If this plugin is activated in the future, run this function.”


Step 6 — Additional Files Are Loaded

Next we load:

includes/class-loader.php

PHP reads the file.

The class becomes available.

Then we create:

new Flipnzee_Auction_Loader();

Its constructor executes immediately.

Right now the constructor is empty.

Later it will register all our actions, filters, REST routes, AJAX handlers, database classes, auction engine, payment system, and more.


The Complete Lifecycle

Here’s the entire process.

Browser Request
        │
        ▼
Web Server
        │
        ▼
WordPress Starts
        │
        ▼
Find Active Plugins
        │
        ▼
Read Plugin Header
        │
        ▼
Execute PHP
        │
        ▼
Security Check
        │
        ▼
Define Constants
        │
        ▼
Register Hooks
        │
        ▼
Load Classes
        │
        ▼
Plugin Ready

Every page request follows this sequence.


What Happens During Activation?

Activation is different.

It occurs only when you click:

Activate Plugin

WordPress performs:

Load Plugin

↓

Execute Activation Function

↓

Return Control

Your activation function currently contains:

flush_rewrite_rules();

Later we’ll expand it to:

  • Create database tables.
  • Create default settings.
  • Set plugin version.
  • Prepare the auction system.

What Happens During Deactivation?

When you deactivate the plugin:

WordPress executes:

flipnzee_auction_deactivate()

Currently it refreshes rewrite rules.

Later we’ll also:

  • Remove scheduled tasks.
  • Clear caches.
  • Perform cleanup.

Notice that we won’t delete auction data during deactivation.

Users expect their data to remain if they reactivate the plugin later.


Understanding One Important Difference

Many beginners think:

Activation happens every time a page loads.

It doesn’t.

Activation occurs once.

Normal plugin loading occurs on every request.

Understanding this distinction prevents many common mistakes.


Improving Our Development Workflow

As our project grows, we’ll start generating temporary files such as plugin ZIP archives.

Rather than storing these generated files in Git, professional developers use a .gitignore file to tell Git which files should be ignored.

In the next lesson, we’ll create our first .gitignore file and begin adopting even more professional development practices.


Lesson Summary

In this lesson, we explored the complete lifecycle of a WordPress plugin. We learned how WordPress discovers plugins, reads plugin headers, executes PHP code, registers hooks, and loads classes. We also distinguished between activation, deactivation, and the normal loading process that occurs on every page request.

Understanding this lifecycle gives us a strong conceptual foundation for every feature we’ll build in the remaining lessons.


Key Takeaways

  • ✓ WordPress loads active plugins during every request.
  • ✓ Plugin headers are read by WordPress, not PHP.
  • ABSPATH exists because WordPress has already started.
  • ✓ Activation hooks are registered during loading but execute only during activation.
  • ✓ Classes are loaded only after the main plugin file executes.
  • ✓ Understanding the plugin lifecycle makes debugging much easier.

Common Mistakes

  • Assuming activation hooks run on every page load.
  • Placing expensive operations outside hooks, causing them to execute on every request.
  • Removing user data during plugin deactivation.
  • Confusing plugin registration with plugin execution.

Git Commands Used

git add .

git commit -m "Lesson 3: Understand the WordPress plugin lifecycle"

git push

Project Status

✅ Development environment

✅ Plugin skeleton

✅ Plugin installation

✅ Plugin lifecycle

⬜ .gitignore

⬜ Database tables

⬜ Auction data model

⬜ Bidding engine

⬜ Escrow

⬜ Payment gateways

⬜ Website transfer

⬜ Version 1.0

Developer’s Notebook

One of the biggest milestones in becoming a professional WordPress developer is realizing that your code doesn’t run in isolation. Every plugin participates in WordPress’s startup process. The better you understand that process, the easier it becomes to write efficient, secure, and maintainable plugins. Throughout the remainder of this series, we’ll continue referring back to the plugin lifecycle as we introduce hooks, database tables, AJAX, REST APIs, and background tasks.


Looking Ahead

In Lesson 4, we’ll create our first .gitignore file and begin preparing the project for long-term development. We’ll also discuss which files belong in Git, which should never be committed, and why professional developers treat generated files differently from source code.