I have a WordPress site installed at the root of the webserver: / I want to serve a text file from here: /.hiddendir/secondlevel/textfile

I've created this file and directory on the server at ../wwwroot/.hiddendir/secondlevel/textfile

When i try to access, I get a 404 from wordpress.

The reason is to add a required "verification" file for a service i want to use, it looks at a url to ensure you own the website (yahoo and google do this for their various services).

I've tried to add the text file to the media gallery, but the url cannot be customized. I am looking for a plugin, but cannot find one.


You can utilize add_rewrite_rule to create a new endpoint like http://example.com/api/files/xyz which processes the request and renders the contents from your server. This allows you to mask the origin of the file but still access it's content.

add_rewrite_rule requires you flush_rewrite_rules but you only need to do that once every time you make a change to your rewrites. So essentially leave that one line in to test but take it out during production.

Once you've determined that the url is requesting a file, and which file you want present, do a quick is_readable check to make sure the file exists and you have access to the content.

At that point you can write some headers to describe the file, read the contents and write to the output buffer with readfile.

You can drop this in your functions.php or in a plugin to allow access regardless of theme.

Descriptions are in the code comments.


if ( ! class_exists( 'FileEndpoint' ) ):

    class FileEndpoint {
        const ENDPOINT_QUERY_NAME  = 'api/files';
        const ENDPOINT_QUERY_PARAM = '__api_files';

        // WordPress hooks

        public function init() {
            add_filter( 'query_vars', array ( $this, 'add_query_vars' ), 0 );
            add_action( 'parse_request', array ( $this, 'sniff_requests' ), 0 );
            add_action( 'init', array ( $this, 'add_endpoint' ), 0 );

        // Add public query vars

        public function add_query_vars( $vars ) {

            // add all the things we know we'll use

            $vars[] = static::ENDPOINT_QUERY_PARAM;
            $vars[] = 'file';

            return $vars;

        // Add API Endpoint

        public function add_endpoint() {
            add_rewrite_rule( '^' . static::ENDPOINT_QUERY_NAME . '/([^/]*)/?', 'index.php?' . static::ENDPOINT_QUERY_PARAM . '=1&file=$matches[1]', 'top' );

            flush_rewrite_rules( false ); //// <---------- REMOVE THIS WHEN DONE

        // Sniff Requests

        public function sniff_requests( $wp_query ) {
            global $wp;

            if ( isset(
                $wp->query_vars[ static::ENDPOINT_QUERY_PARAM ],
                $wp->query_vars[ 'file' ] ) ) {
                $this->handle_file_request(); // handle it

        // Handle Requests

        protected function handle_file_request() {
            global $wp;

            $file     = $wp->query_vars[ 'file' ];
            $filepath = '';
            switch ( $file ) {

                // example.com/api/files/xyz
                case 'xyz':
                    $filepath = __DIR__ . '/filename.txt';

            if ( ! empty( $filepath ) ) {

                // Make sure this is an accessible file
                // If we can't read it throw an Error
                if ( ! is_readable( $filepath ) ) {

                    $err = new WP_Error( "Forbidden", "Access is not allowed for this request.", 403 );
                    wp_die( $err->get_error_message(), $err->get_error_code() );

                // We can read it, so let's render it
                $this->serve_file( $filepath );

            // Nothing happened, just give some feedback
            $err = new WP_Error( "Bad Request", "Invalid Request.", 400 );
            wp_die( $err->get_error_message(), $err->get_error_code() );

        // Output the file

        protected function serve_file( $filepath, $force_download = false ) {
            if ( ! empty ( $filepath ) ) {

                // Write some headers

                header( "Cache-control: private" );
                if ( $force_download ) {

                    // Allow a forced download
                    header( "Content-type: application/force-download" );
                    header( "Content-disposition: attachment; filename=\"filename.txt\"" );
                header( "Content-transfer-encoding: binary\n" );
                header( "Content-Length: " . filesize( $filepath ) );

                // render the contents of the file
                readfile( $filepath );

                // kill the request. Nothing else to do now.

            // nothing happened, :(
            return false;

    $wpFileEndpoint = new FileEndpoint();

endif; // FileEndpoint
  • Ok, i'll look into this. Should I assume you do not know of a already made plugin for this? Perhaps you can suggest good search terms. I am thinking API might be good, as you have suggested a similar use case. Mar 19 '16 at 16:01
  • This site isn't for plugin recommendations.
    – jgraup
    Mar 19 '16 at 16:08
  • Did this help you?
    – jgraup
    May 29 '16 at 15:40
  • Not sure yet. I had a window of time to accomplish this task, so I had to take another route, server configuration changes to allow the file to be served. One day, if I have more time, I'll get back around to this. Jun 8 '16 at 18:56
  • So the only reason i haven't accepted it is because I haven't tried it. I appreciate your answer. Jun 8 '16 at 18:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.