Actually if you look closely to the codex, you can see that setting 'post_type'
argument to any
will by default leave out some post types from the query: https://codex.wordpress.org/Class_Reference/WP_Query#Type_Parameters
‘any’ – retrieves any type except revisions and types with ‘exclude_from_search’ set to true.
So you can actually register a post type and make it non-public, there’s a bunch of arguments there as you probably know, so you could work with these:
$args = array(
'public' => false,
'show_ui' => false,
'show_in_menu' => false,
'show_in_admin_bar' => false,
'show_in_nav_menus' => false,
'show_in_rest' => false,
'can_export' => false,
'has_archive' => false,
'exclude_from_search' => true, // this one is important!
'publicly_queryable' => false,
);
register_post_type( 'hidden_post_type', $args );
Taking this under consideration, I’d say that most of the time using CPT is the right way to go, and many popular plugins are doing this, using non-public posts.
You can always look for an extreme solution like building additional tables in database for your plugin, but this isn’t a WordPress way to go in my opinion. By using CPT you are getting a bunch of goodies out of the box, such as caching and you don’t have to worry about future compatibility.