This is the first post in a series relating to Project Backlog. The first step was to review the list of projects to see where the starting point should be. This was a time-consuming process, considering one of the rationale for the project is to get a better sense of the backlog itself. What I ended up with was a list of titles, and the prospect of creating a year’s worth of posts.
Bulk Post Generation
I wanted to create the draft posts immediately, so that I could easily update any of them at any time. Initially, I created a simple bulk generation script which created the draft posts easily enough from a list of titles. I added form inputs for the post type and post status, and it all worked pretty well.

Information Architecture
When building a website of any substantial size, one of the first steps is developing the information architecture. This often involves creating a multi-level hierarchy of pages. In WordPress, pages are a post type that allows you to set parent-child relationships. I wanted a way to easily input a list of pages, define their hierarchy, and automatically assign parent pages.
The goal is to save significant time on large projects. Structuring pages manually can be tedious and time-consuming. Common solutions often involve configuring CSV files or using other cumbersome methods. Instead, I focused on simplicity. For large websites, this approach could reduce hours of work to just a few minutes.


Agentic AI and Process Improvement
With the recent surge of interest in Agentic AI, there’s a growing assumption that AI will autonomously identify process inefficiencies and suggest improvements. I’m not convinced AI can meaningfully improve most business workflows in this way. To do so, it would need a deep understanding of the various components involved—something that, in most cases, humans grasp better. People inherently recognize where their time is wasted and seek freedom from repetitive, uninteresting tasks.
The real value of AI lies elsewhere—particularly in leveraging large language models (LLMs) to accelerate solution development. Instead of spending hours manually creating basic tools, an LLM can generate all-purpose solutions quickly, adhering to best practices and emphasizing reusability. This means we can easily create tools that were once too time-consuming to build, speeding up other development tasks in the process.
The Plugin Code
The plugin is available on GitHub. You can also see the three files below.
https://github.com/revnoah/wordpress-draft-post-generator
draft-post-generator.php
<?php
/**
* Plugin Name: Draft Post Generator
* Description: Quickly generate draft posts from new line-delimited titles with taxonomy, post status, and menu options.
* Version: 1.0.0
* Author: Noah Stewart
* Text Domain: draft-post-generator
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
// Define paths
define('DRAFT_POST_GEN_PATH', plugin_dir_path(__FILE__));
define('DRAFT_POST_GEN_URL', plugin_dir_url(__FILE__));
// Autoload files
require_once DRAFT_POST_GEN_PATH . 'includes/class-draft-post-creator.php';
require_once DRAFT_POST_GEN_PATH . 'admin/draft-post-admin.php';
// Initialize plugin
add_action('plugins_loaded', ['Draft_Post_Creator', 'init']);
add_action('init', ['Draft_Post_Admin', 'init']);
add_action('admin_menu', ['Draft_Post_Admin', 'admin_menu']);
admin/draft-post-admin.php
<?php
if (!defined('ABSPATH')) {
exit;
}
class Draft_Post_Admin {
public static function init() {
add_action('admin_post_draft_post_generator', [__CLASS__, 'process_form']);
}
public static function admin_menu() {
add_submenu_page(
'tools.php',
__('Draft Post Generator', 'draft-post-generator'),
__('Draft Post Generator', 'draft-post-generator'),
'manage_options',
'draft-post-generator',
[__CLASS__, 'render_admin_page']
);
}
public static function render_admin_page() {
if ($message = get_transient('draft_post_generator_message')) {
echo '<div class="notice notice-success is-dismissible">';
echo '<p>' . esc_html($message) . '</p>';
echo '</div>';
delete_transient('draft_post_generator_message');
}
?>
<div class="wrap">
<h1><?php _e('Draft Post Generator', 'draft-post-generator'); ?></h1>
<form method="post" action="<?php echo esc_url( admin_url('admin-post.php') ); ?>">
<input type="hidden" name="action" value="draft_post_generator">
<?php wp_nonce_field('draft_post_generator_action', 'draft_post_generator_nonce'); ?>
<table class="form-table">
<tr>
<th><?php _e('Post Titles', 'draft-post-generator'); ?></th>
<td>
<textarea name="post_titles" rows="10" cols="50" required></textarea>
</td>
</tr>
<tr>
<th><?php _e('Post Type', 'draft-post-generator'); ?></th>
<td>
<select name="post_type">
<?php
$post_types = get_post_types(['public' => true], 'objects');
foreach ($post_types as $type) {
echo '<option value="' . esc_attr($type->name) . '">' . esc_html($type->label) . '</option>';
}
?>
</select>
</td>
</tr>
<tr>
<th><?php _e('Post Status', 'draft-post-generator'); ?></th>
<td>
<select name="post_status">
<?php
$statuses = get_post_statuses();
foreach ($statuses as $status => $label) {
echo '<option value="' . esc_attr($status) . '" ' . selected($status, 'draft', false) . '>' . esc_html($label) . '</option>';
}
?>
</select>
</td>
</tr>
</table>
<input type="submit" class="button-primary" value="<?php _e('Generate Drafts', 'draft-post-generator'); ?>">
</form>
</div>
<?php
}
public static function process_form() {
if (!isset($_POST['draft_post_generator_nonce']) || !wp_verify_nonce($_POST['draft_post_generator_nonce'], 'draft_post_generator_action')) {
wp_die(__('Nonce verification failed', 'draft-post-generator'));
}
if (!current_user_can('manage_options')) {
wp_die(__('You do not have permission.', 'draft-post-generator'));
}
$titles = sanitize_textarea_field($_POST['post_titles']);
$post_type = sanitize_text_field($_POST['post_type']);
$post_status = sanitize_text_field($_POST['post_status']);
$created_count = Draft_Post_Creator::create_drafts_from_titles($titles, $post_type, [], null, '', $post_status);
$post_type_object = get_post_type_object($post_type);
$post_status_object = get_post_status_object($post_status);
$post_type_label = $post_type_object ? strtolower($post_type_object->labels->singular_name) : $post_type;
$post_status_label = $post_status_object ? strtolower($post_status_object->label) : $post_status;
set_transient('draft_post_generator_message', sprintf(__('%d %s %s created successfully.', 'draft-post-generator'), $created_count, $post_status_label, $post_type_label), 30);
wp_redirect(admin_url('tools.php?page=draft-post-generator'));
exit;
}
}
includes/class-draft-post-creator.php
<?php
if (!defined('ABSPATH')) {
exit;
}
class Draft_Post_Creator {
public static function init() {
// Placeholder for future hooks
}
public static function create_drafts_from_titles($titles, $post_type, $taxonomy_terms, $menu_id, $default_content, $post_status) {
if (empty($titles)) {
return false;
}
$parent_map = [0];
$lines = explode("\n", $titles);
$created_count = 0;
foreach ($lines as $title) {
preg_match('/^(-+)?(.*)$/', $title, $matches);
$hyphen_count = isset($matches[1]) ? strlen($matches[1]) : 0;
$clean_title = trim($matches[2]);
$parent_level = max(0, $hyphen_count);
$parent_id = isset($parent_map[$parent_level]) ? $parent_map[$parent_level] : 0;
$post_id = self::insert_post($clean_title, $parent_id, $post_type, $taxonomy_terms, $menu_id, $default_content, $post_status);
if ($post_id) {
$created_count++;
$parent_map[$parent_level + 1] = $post_id;
}
}
return $created_count;
}
private static function insert_post($title, $parent_id, $post_type, $taxonomy_terms, $menu_id, $default_content, $post_status) {
$post_data = [
'post_title' => sanitize_text_field($title),
'post_content' => $default_content,
'post_status' => $post_status,
'post_type' => $post_type,
'post_parent' => $parent_id,
'post_author' => get_current_user_id(),
];
$post_id = wp_insert_post($post_data);
if(!is_wp_error($post_id)){
} else {
echo $post_id->get_error_message();
}
if ($post_id && !empty($taxonomy_terms)) {
wp_set_post_terms($post_id, $taxonomy_terms, get_object_taxonomies($post_type)[0]);
}
return $post_id;
}
}
Conclusion
This plugin tackles a small problem with a simple solution. When you’re deep in the midst of work, stepping back to make small process tweaks can feel impossible. Time and attention are usually pulled in other directions, but setting aside even a little time for these improvements can be worthwhile—for both your workflow and your sanity.