I'd like to add a page that does not appear anywhere on the site (especially not in the header or wherever your pages are listed) unless the URL is typed in directly. Additionally, the page/post should be password-protected.

I know how to add a password, but I found no way to remove the page from being listed.

By design, wordpress does not have a property per post or page that would signal the application to not use the page in queries of the standard controller WP_Query as you asked for.

However, it's not that this scenario is something too far away. Let's look what wordpress has to offer instead.

Wordpress Post Statuses

Posts can have some status, or more precisely, they have a status. That is what you might know already. I list them with their string identifier, their naming and context if any. Every post has one of these:

It's not very well documented, I assume because the concept of post statuses was not introduced as a fixed feature set but on best try and then having a look. So expect this to be a bit fragile and it's unknown if it can be used to actually solve your issue. But I would nevertheless stick to it, because it's a built-in feature which is to be expected, like custom-post-types, to be integrated more and more with each release. CPT's for example are not introduced since 2.8 IIRC and still not complete. It just takes some time.

This post status registration function is documented as being available since 3.0.0, so it's quite new. It was introduced in 12719, related Ticket is #9674.

You can imagine, that the concept is not well integrated so far.

But it's worth to take a look at that end, because it looks like it contains all ingredients which are important for your feature request. Post statuses have properties that are used in the controllers and models decisions to fetch content - that's basically why you like to control. Let's look what's in before I continue with what's not working / buggy:

publicTRUE/FALSE - Known to be TRUE for Published posts. So post statuses with the public-property set to TRUE are assume to behave like published posts.

privateTRUE/FALSE - Known to be TRUE for Private posts. Comparable to the public-property.

_builtin - Optional, internal switch. Can be used to create built-in post statuses.

Those post status properties are not designed to work with each other. The concept more or less is that everyone of those is FALSE by default and you can set some for them to TRUE. The data-structure in wordpress is a standard class with some global accessible properties. It is added to the $wp_post_statuses global variable which acts like a singleton registry in the global namespace per request, ready to be used after init. So if you would like to see, what has been defined, add a hook at the end of init for debugging purposes:

With something like that you can see what's in for the moment. As those are standard classes with public properties, nothing is guaranteeing data consistency here. So take care as usual with the wordpress API. Some of those most probably interfere with the functionality of others but how and when is only defined by code which is likely to change. Just take a bit care and double check with the source.

Limitations so far

So as it looks good so far to make use of post statuses, let's take a look for the expected to be limitations this comes with.

The admin lacks of options to make own post states available in the publish box for example. that means, you need to either patch the existing display routine or create your own UI so far for it. Patching core might be more probable as it allows you to offer code for an existing feature request (Ticket #12567) and an own post edit meta box might be interactively be interfering with the concept of the publish box.

I suggest some lightweight patch to introduce a new filter that allows to change the listing of radios. The lighter your approach is, the more likely you will get this in. I smell that core developers fear a bit to introduce any changes to that part because of it's complexitiy. So the less complex your changes are, the better this is.

Additionally, even if my list above suggest that there is some context for post or page, you can not setup a post status for a specific post type AFAIK. So expect that your post status is available to any custom post type as well or at least the behavior needs to be researched for any of those and properly tested on your concrete site.

So to learn about the deficiencies, it's always good to peek what's reported in trac to learn about limitations upfront. I do this by searching for the function name (and I use google to search trac next to trac search):

Defining your own Post Status

So one option might be to define your own post status. That would enable you to re-use existing status and their functionality (namely drafting and trashing) while maintaining your own type for your hiding purposes.

I would setup one as public = TRUE; but with publicly_queryable = FALSE;. Then I don't know your needs regarding URL design, so using a numeric ID might be okay. This could short-circuit all the permalinks hassles this concept might have. Instead, if identified as single request with a numeric ID, you can introduce some code, that sets public_queryable to true on for the single request (single means here, the page-data is requested directly and not for a listing). Some pseudo code:

Set within the right place which would be prior to the actual fetching of the models from the database but after you are able to identify the type of request (see Wordpress 3.0 Program Flow (Toolpress PDF) with the need to trace hooks in WP_Query and related), this can work.

It would offer a quite lightweight integration for your feature while making use of core elements to integrate it. Probably some of the plugins listed by Mike are already making use of custom post statuses, so there would already be example code.