From 8cf4ad9535c2f953deafe7d4f76a853c2212bb3f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 08:32:24 +0000 Subject: [PATCH 1/3] Document the WP_Query arguments `wp post list` accepts The command passes what it is given to WP_Query, so a good deal works that nothing mentions. This documents the arguments that filter on what the command displays, the way #642 did for `wp site list`. Three of the fields it displays are named after the wp_posts column rather than the WP_Query argument that filters on it, and passing the column name reached WP_Query as an argument it does not know: it was dropped, and every post came back - a filter that reads as if it works and quietly does not. $ wp post list --post_title='Hello world!' --format=count 2 wp-cli resolves parameter aliases before a command runs, so declaring them in the synopsis is enough to make the column spellings work: - --title, with --post_title as an alias - --name, with --post_name as an alias - --author, with --post_author as an alias - --p, with --ID as an alias Everything else here already filtered and is only being written down. Each one was checked against WP_Query rather than assumed: 'post__in', 'post_name__in' and 'author__in' are left out because they are only read as arrays, so a comma-separated value would quietly match on the first entry alone, and making them usable is more than documentation. '--name' keeps WP_Query's behaviour of making the query a single-post one, which returns a draft only to a user who can edit it. Running as no user, WP-CLI cannot, so the note says to filter drafts another way rather than rerouting the argument to mean something WP_Query does not. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014SSZzqMJRDTiLiDxQEPYcL --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++-- features/post.feature | 58 +++++++++++++++++++++++++++++++++++ src/Post_Command.php | 69 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 195 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7760bf5b4..6fed41245 100644 --- a/README.md +++ b/README.md @@ -3150,7 +3150,7 @@ wp post get [--field=] [--fields=] [--format=] Gets a list of posts. ~~~ -wp post list [--=] [--field=] [--fields=] [--format=] +wp post list [--=] [--p=|ID] [--title=|post_title] [--name=<slug>|post_name] [--author=<author>|post_author] [--author_name=<author_name>] [--post_type=<post_type>] [--post_status=<post_status>] [--post_parent=<post_parent>] [--post_mime_type=<post_mime_type>] [--menu_order=<menu_order>] [--comment_status=<comment_status>] [--ping_status=<ping_status>] [--comment_count=<comment_count>] [--s=<string>] [--year=<year>] [--monthnum=<monthnum>] [--day=<day>] [--m=<yearmonth>] [--w=<week>] [--field=<field>] [--fields=<fields>] [--format=<format>] ~~~ Display posts based on all arguments supported by [WP_Query()](https://developer.wordpress.org/reference/classes/wp_query/). @@ -3159,7 +3159,74 @@ Only shows post types marked as post by default. **OPTIONS** [--<field>=<value>] - One or more args to pass to WP_Query. + One or more args to pass to WP_Query. The arguments below are the ones + that filter on what this command displays; anything else WP_Query accepts + still works and is documented with WP_Query itself. + + [--p=<id>|ID] + Filter by post ID. `--ID` is the name of the column this filters and is + accepted as an alias. + + [--title=<title>|post_title] + Filter by post title, matched in full. `--post_title` is the name of the + column this filters and is accepted as an alias. + + [--name=<slug>|post_name] + Filter by post slug. `--post_name` is the name of the column this filters + and is accepted as an alias. + Note: this makes the query a single-post one, and WP_Query only returns a + draft from one of those to a user who can edit it - which, running as no + user, WP-CLI cannot. Pass `--post_status=draft` with `--author` instead to + find drafts. + + [--author=<author>|post_author] + Filter by the ID of the post's author. `--post_author` is the name of the + column this filters and is accepted as an alias. + + [--author_name=<author_name>] + Filter by the 'user_nicename' of the post's author. + + [--post_type=<post_type>] + Filter by post type. Defaults to 'post'; pass 'any' for every type. + + [--post_status=<post_status>] + Filter by post status. Pass 'any' for every status. + + [--post_parent=<post_parent>] + Filter by the ID of the parent post. + + [--post_mime_type=<post_mime_type>] + Filter by MIME type. Only attachments carry one. + + [--menu_order=<menu_order>] + Filter by menu order. + + [--comment_status=<comment_status>] + Filter by comment status. Accepts 'open' or 'closed'. + + [--ping_status=<ping_status>] + Filter by ping status. Accepts 'open' or 'closed'. + + [--comment_count=<comment_count>] + Filter by number of comments. + + [--s=<string>] + Only list the posts matching this search term. + + [--year=<year>] + Filter by four-digit year, e.g. 2024. + + [--monthnum=<monthnum>] + Filter by month number, 1 to 12. + + [--day=<day>] + Filter by day of the month, 1 to 31. + + [--m=<yearmonth>] + Filter by year and month together, e.g. 202401. + + [--w=<week>] + Filter by week of the year, 0 to 53. [--field=<field>] Prints the value of a single field for each post. diff --git a/features/post.feature b/features/post.feature index 02a845e01..4ed7ba7f8 100644 --- a/features/post.feature +++ b/features/post.feature @@ -591,3 +591,61 @@ Feature: Manage WordPress posts """ {"block_version":1} """ + + Scenario: Filtering by the wp_posts column names + When I run `wp post create --post_title='Alpha' --post_status=publish --porcelain` + Then STDOUT should be a number + + # 'title', 'name' and 'author' are WP_Query's names for these filters. The + # columns they filter on are spelled differently, and passing the column + # name used to reach WP_Query as an argument it does not know: it was + # dropped, and every post came back. They are aliases now. + When I run `wp post list --title='Hello world!' --format=count` + Then STDOUT should be: + """ + 1 + """ + + When I run `wp post list --post_title='Hello world!' --format=count` + Then STDOUT should be: + """ + 1 + """ + + When I run `wp post list --name=alpha --format=count` + Then STDOUT should be: + """ + 1 + """ + + When I run `wp post list --post_name=alpha --format=count` + Then STDOUT should be: + """ + 1 + """ + + # Only the bundled post matches: a post created by WP-CLI has author 0, + # because WP-CLI runs as no user unless told otherwise. + When I run `wp post list --author=1 --format=count` + Then STDOUT should be: + """ + 1 + """ + + When I run `wp post list --post_author=1 --format=count` + Then STDOUT should be: + """ + 1 + """ + + When I run `wp post list --p=1 --format=count` + Then STDOUT should be: + """ + 1 + """ + + When I run `wp post list --ID=1 --format=count` + Then STDOUT should be: + """ + 1 + """ diff --git a/src/Post_Command.php b/src/Post_Command.php index fd472fbea..6a64ef96e 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -617,7 +617,74 @@ protected function delete_callback( $post_id, $assoc_args ) { * ## OPTIONS * * [--<field>=<value>] - * : One or more args to pass to WP_Query. + * : One or more args to pass to WP_Query. The arguments below are the ones + * that filter on what this command displays; anything else WP_Query accepts + * still works and is documented with WP_Query itself. + * + * [--p=<id>|ID] + * : Filter by post ID. `--ID` is the name of the column this filters and is + * accepted as an alias. + * + * [--title=<title>|post_title] + * : Filter by post title, matched in full. `--post_title` is the name of the + * column this filters and is accepted as an alias. + * + * [--name=<slug>|post_name] + * : Filter by post slug. `--post_name` is the name of the column this filters + * and is accepted as an alias. + * Note: this makes the query a single-post one, and WP_Query only returns a + * draft from one of those to a user who can edit it - which, running as no + * user, WP-CLI cannot. Pass `--post_status=draft` with `--author` instead to + * find drafts. + * + * [--author=<author>|post_author] + * : Filter by the ID of the post's author. `--post_author` is the name of the + * column this filters and is accepted as an alias. + * + * [--author_name=<author_name>] + * : Filter by the 'user_nicename' of the post's author. + * + * [--post_type=<post_type>] + * : Filter by post type. Defaults to 'post'; pass 'any' for every type. + * + * [--post_status=<post_status>] + * : Filter by post status. Pass 'any' for every status. + * + * [--post_parent=<post_parent>] + * : Filter by the ID of the parent post. + * + * [--post_mime_type=<post_mime_type>] + * : Filter by MIME type. Only attachments carry one. + * + * [--menu_order=<menu_order>] + * : Filter by menu order. + * + * [--comment_status=<comment_status>] + * : Filter by comment status. Accepts 'open' or 'closed'. + * + * [--ping_status=<ping_status>] + * : Filter by ping status. Accepts 'open' or 'closed'. + * + * [--comment_count=<comment_count>] + * : Filter by number of comments. + * + * [--s=<string>] + * : Only list the posts matching this search term. + * + * [--year=<year>] + * : Filter by four-digit year, e.g. 2024. + * + * [--monthnum=<monthnum>] + * : Filter by month number, 1 to 12. + * + * [--day=<day>] + * : Filter by day of the month, 1 to 31. + * + * [--m=<yearmonth>] + * : Filter by year and month together, e.g. 202401. + * + * [--w=<week>] + * : Filter by week of the year, 0 to 53. * * [--field=<field>] * : Prints the value of a single field for each post. From c52995ff38bb45335b90a243a61ccb4fa005d651 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Tue, 18 Aug 2026 08:41:13 +0000 Subject: [PATCH 2/3] Point at `--user` for filtering drafts by slug The note on `--name` said to filter drafts some other way, which was a detour. WP_Query hands a draft from a single-post query to a user who can edit it, and the global `--user` argument is how WP-CLI becomes one. Checked rather than assumed, because the earlier advice happened to work for the wrong reason: wp post list --name=beta --field=ID -> empty wp post list --name=beta --post_status=any --field=ID -> empty wp post list --name=beta --user=1 --field=ID -> found '--post_status=draft' also reaches it, but only because naming the post's own status sidesteps the check rather than satisfying it, and '--post_status=any' does not. '--user' is the answer to what the note is actually about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SSZzqMJRDTiLiDxQEPYcL --- README.md | 8 ++++---- features/post.feature | 13 +++++++++++++ src/Post_Command.php | 8 ++++---- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6fed41245..9554cd473 100644 --- a/README.md +++ b/README.md @@ -3174,10 +3174,10 @@ Only shows post types marked as post by default. [--name=<slug>|post_name] Filter by post slug. `--post_name` is the name of the column this filters and is accepted as an alias. - Note: this makes the query a single-post one, and WP_Query only returns a - draft from one of those to a user who can edit it - which, running as no - user, WP-CLI cannot. Pass `--post_status=draft` with `--author` instead to - find drafts. + Note: this makes the query a single-post one, and WP_Query returns a draft + from one of those only to a user who can edit it. WP-CLI runs as no user + unless the global `--user` argument says otherwise, so pass that to filter + drafts by slug. [--author=<author>|post_author] Filter by the ID of the post's author. `--post_author` is the name of the diff --git a/features/post.feature b/features/post.feature index 4ed7ba7f8..59406be07 100644 --- a/features/post.feature +++ b/features/post.feature @@ -649,3 +649,16 @@ Feature: Manage WordPress posts """ 1 """ + + Scenario: Filtering drafts by slug needs a user + When I run `wp post create --post_title='Beta' --post_name=beta --post_status=draft --porcelain` + Then STDOUT should be a number + + # '--name' makes this a single-post query, and WP_Query hands a draft from + # one of those only to a user who can edit it. WP-CLI is no user by default. + When I run `wp post list --name=beta --field=ID` + Then STDOUT should be empty + + # The global '--user' argument is what makes it reachable. + When I run `wp post list --name=beta --user=1 --field=ID` + Then STDOUT should not be empty diff --git a/src/Post_Command.php b/src/Post_Command.php index 6a64ef96e..f7e095d16 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -632,10 +632,10 @@ protected function delete_callback( $post_id, $assoc_args ) { * [--name=<slug>|post_name] * : Filter by post slug. `--post_name` is the name of the column this filters * and is accepted as an alias. - * Note: this makes the query a single-post one, and WP_Query only returns a - * draft from one of those to a user who can edit it - which, running as no - * user, WP-CLI cannot. Pass `--post_status=draft` with `--author` instead to - * find drafts. + * Note: this makes the query a single-post one, and WP_Query returns a draft + * from one of those only to a user who can edit it. WP-CLI runs as no user + * unless the global `--user` argument says otherwise, so pass that to filter + * drafts by slug. * * [--author=<author>|post_author] * : Filter by the ID of the post's author. `--post_author` is the name of the From e824883890019bba081752fe6e7095c43fd9eb2b Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Tue, 18 Aug 2026 08:49:53 +0000 Subject: [PATCH 3/3] Say what 'any' leaves out, and that it is the default 'any' is not "everything". WP_Query reads it as everything registered without 'exclude_from_search', for post types and post statuses alike, and this command defaults post_status to it. The practical consequence had gone unsaid: 'trash' and 'auto-draft' carry that flag, so a trashed post is absent from `wp post list` until its status is named. Checked rather than taken on faith: post_type=any, exclude_from_search=false type -> included post_type=any, exclude_from_search=true type -> excluded post_status=any, draft -> included post_status=any, trashed -> excluded post_status=trash, trashed -> included Raised by CodeRabbit on the pull request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014SSZzqMJRDTiLiDxQEPYcL --- README.md | 7 +++++-- features/post.feature | 26 ++++++++++++++++++++++++++ src/Post_Command.php | 7 +++++-- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9554cd473..8aa6c4f16 100644 --- a/README.md +++ b/README.md @@ -3187,10 +3187,13 @@ Only shows post types marked as post by default. Filter by the 'user_nicename' of the post's author. [--post_type=<post_type>] - Filter by post type. Defaults to 'post'; pass 'any' for every type. + Filter by post type. Defaults to 'post'. Accepts a comma-separated list, + or 'any' for every type registered without 'exclude_from_search'. [--post_status=<post_status>] - Filter by post status. Pass 'any' for every status. + Filter by post status. Defaults to 'any', which is every status + registered without 'exclude_from_search' - so trashed and auto-draft posts + are left out until asked for by name, e.g. `--post_status=trash`. [--post_parent=<post_parent>] Filter by the ID of the parent post. diff --git a/features/post.feature b/features/post.feature index 59406be07..7d7571e1b 100644 --- a/features/post.feature +++ b/features/post.feature @@ -662,3 +662,29 @@ Feature: Manage WordPress posts # The global '--user' argument is what makes it reachable. When I run `wp post list --name=beta --user=1 --field=ID` Then STDOUT should not be empty + + Scenario: Trashed posts need their status named + When I run `wp post create --post_title='Doomed' --post_status=publish --porcelain` + Then STDOUT should be a number + And save STDOUT as {DOOMED_ID} + + When I run `wp post delete {DOOMED_ID}` + Then STDOUT should contain: + """ + Success: Trashed post + """ + + # This command defaults post_status to 'any', and WP_Query reads 'any' as + # every status registered without 'exclude_from_search' - which leaves out + # 'trash' and 'auto-draft'. + When I run `wp post list --field=ID` + Then STDOUT should not contain: + """ + {DOOMED_ID} + """ + + When I run `wp post list --post_status=trash --field=ID` + Then STDOUT should contain: + """ + {DOOMED_ID} + """ diff --git a/src/Post_Command.php b/src/Post_Command.php index f7e095d16..ab3a26f79 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -645,10 +645,13 @@ protected function delete_callback( $post_id, $assoc_args ) { * : Filter by the 'user_nicename' of the post's author. * * [--post_type=<post_type>] - * : Filter by post type. Defaults to 'post'; pass 'any' for every type. + * : Filter by post type. Defaults to 'post'. Accepts a comma-separated list, + * or 'any' for every type registered without 'exclude_from_search'. * * [--post_status=<post_status>] - * : Filter by post status. Pass 'any' for every status. + * : Filter by post status. Defaults to 'any', which is every status + * registered without 'exclude_from_search' - so trashed and auto-draft posts + * are left out until asked for by name, e.g. `--post_status=trash`. * * [--post_parent=<post_parent>] * : Filter by the ID of the parent post.