Customizing the WordPress Install Process with install.php

No more "This is your first post!"

I came across a super interesting post at WPBits about [automating the wordpress installation][1] process.  Some of the parts about where some of the functions are located in WP that allow you to change certain things on the installation are outdated since the post is from 2007, but the concept remains perfectly intact.  If you find yourself installing a lot of WordPress blogs and are tired of always having to delete the “This is your First Post” post, changing the permalinks etc. then you can take advantage of this little trick.

Trolling the Source Code Like a Boss

If you are cruising through source (because there is plenty of stuff that isn’t well documented yet in the Codex) you will see

/** Include user install customize script. */ 
if ( file_exists(WP_CONTENT_DIR . '/install.php') ) require (WP_CONTENT_DIR . '/install.php');

/wp-admin/includes/upgrade.php.  Now this is interesting because it means that when WordPress installs (or upgrades) it will look for a file called install.php in your /wp-content folder… or whatever you are using instead of the default /wp-content folder.  This file doesn’t exist by default but it gives you a chance to run some functions of your own during the installation process (since  /wp-admin/includes/upgrade.php is required by /wp-admin/install.php)

My First Custom Install.php

I followed WPBit’s example and decided to show off my new power over WordPress by putting the following into my custom /wp-content/install.php file:

No Bacon for You <br/>Evil Superpowers <?php exit();?> 

is obviously isn’t helpful, but it is a good proof of concept and let’s you know that you have all the power.  Of course…

With great power comes great responsibility. – Uncle Ben

Functions We Can Hijack for Our Own Purposes

As WPBits explains in more detail, some of the ideal to tweak would be:

  • wp_install()
  • wp_install_defaults()
  • wp_new_blog_notification()
  • wp_upgrade()

All of these functions are now (currently version 3.3.1) in /wp-admin/includes/upgrade.php and they are pluggable, meaning they come with an if(function_exists()) wrapper that lets you define your own versions to override their behavior. Since wp_install_defaults() is the culprit for creating those useless “first post” posts and pages, we’re going to target it.  I copied the entire function to my own file: /wp-content/install.php and then hacked out all the parts that inserted the first post, first page, and changed the default category from Uncategorized to General.  I even cut down the number of widgets that are activated by default. I also wanted to change some options, like permalinks, time zone, size of the post editor, banning emoticons, etc.  So I added a bunch of update_option() commands.  Just because I thought it’d be super clever, I updated the ping list, moderate and blacklist comment terms from text files by using file_get_contents().

Automagically Creating Myself as an Admin

I usually install WP for someone else, I decided that I wanted to create my client’s account on install and automatically create my own admin account.  I might reverse this process as I don’t like how the install’s welcome email doesn’t include a link to the login screen.  I know that you just tack on wp-login.php but a newbie client might not.  At the same time, the way I have it means I never have to change my credentials in the file… which I prefer for now.

Anyway, I initially overrode the default wp_install() file to do this, by repeating the process of copying the function from /wp-admin/includes/upgrade.php to my own install.php, but it turns out that I can just create a new user from the function I already have.  In that case, I decided to only override one core function. Here’s the code I used.  Remember to use it at your own risk.  This is really targeted at people who know what they are doing with WordPress and are installing it on a regular basis.  

Take note to change the username and email address for the account you are creating for yourself… currently they are left as USERNAME and YOU@YOUREMAIL.COM.  Right now it generates a random password, but that could easily be changed to always assign yourself the same password.

Edited: Sept 9, 2014 from WordPress 4.0

function wp_install_defaults( $user_id ) {
    global $wpdb, $wp_rewrite, $table_prefix;

    // Default category
    $cat_name = __( 'General' );
    /* translators: Default category slug */
    $cat_slug = sanitize_title( _x( 'General', 'Default category slug' ) );

    if ( global_terms_enabled() ) {
        $cat_id = $wpdb->get_var( $wpdb->prepare( "SELECT cat_ID FROM {$wpdb->sitecategories} WHERE category_nicename = %s", $cat_slug ) );
        if ( $cat_id == null ) {
            $wpdb->insert( $wpdb->sitecategories, array('cat_ID' => 0, 'cat_name' => $cat_name, 'category_nicename' => $cat_slug, 'last_updated' => current_time('mysql', true)) );
            $cat_id = $wpdb->insert_id;
        update_option(  'default_category', $cat_id  );
    } else {
        $cat_id = 1;

    $wpdb->insert( $wpdb->terms, array('term_id' => $cat_id, 'name' => $cat_name, 'slug' => $cat_slug, 'term_group' => 0) );
    $wpdb->insert( $wpdb->term_taxonomy, array('term_id' => $cat_id, 'taxonomy' => 'category', 'description' => '', 'parent' => 0, 'count' => 1));
    $cat_tt_id = $wpdb->insert_id;

    // Set up default widgets for default theme.
    update_option( 'widget_search', array ( 2 => array ( 'title' => '' ), '_multiwidget' => 1 ) );
    update_option( 'sidebars_widgets', array ( 'wp_inactive_widgets' => array (), 'sidebar-1' => array ( 0 => 'search-2' ), 'sidebar-2' => array (), 'sidebar-3' => array (), 'array_version' => 3 ) );

    if ( ! is_multisite() )
        update_user_meta( $user_id, 'show_welcome_panel', 1 );
    elseif ( ! is_super_admin( $user_id ) && ! metadata_exists( 'user', $user_id, 'show_welcome_panel' ) )
        update_user_meta( $user_id, 'show_welcome_panel', 2 );

    if ( is_multisite() ) {
        // Flush rules to pick up the new page.

        $user = new WP_User($user_id);
        $wpdb->update( $wpdb->options, array('option_value' => $user->user_email), array('option_name' => 'admin_email') );

        // Remove all perms except for the login user.
        $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->usermeta WHERE user_id != %d AND meta_key = %s", $user_id, $table_prefix.'user_level') );
        $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->usermeta WHERE user_id != %d AND meta_key = %s", $user_id, $table_prefix.'capabilities') );

        // Delete any caps that snuck into the previously active blog. (Hardcoded to blog 1 for now.) TODO: Get previous_blog_id.
        if ( !is_super_admin( $user_id ) && $user_id != 1 )
            $wpdb->delete( $wpdb->usermeta, array( 'user_id' => $user_id , 'meta_key' => $wpdb->base_prefix.'1_capabilities' ) );

    /* * BEGIN KIA ADDITIONS * Customize Some Options */ 

    // Set Timezone 
    $timezone = "America/Chicago"; 
    update_option( 'timezone_string', $timezone ); 

    // Start of the Week 
    // 0 is Sunday, 1 is Monday and so on 
    update_option( 'start_of_week', 0 ); 

    // Disable Smilies 
    update_option( 'use_smilies', 0 ); 

    // Increase the Size of the Post Editor 
    update_option( 'default_post_edit_rows', 40 ); 

    // Update Ping Services 
    $services = file_exists( WP_CONTENT_DIR . '/KIA-ping-list.txt' ) ? file_get_contents( 'KIA-ping-list.txt', true ) : ''; 
    update_option('ping_sites',$services); } 

    // Update Comment Moderation List 
    $modlist = file_exists( WP_CONTENT_DIR . '/KIA-comment-moderation-list.txt' ) ? file_get_contents( 'KIA-comment-moderation-list.txt', true ) : ''; 
    update_option( 'moderation_keys', $modlist ); } 

    // Update Comment Blacklist 
    $blacklist = file_exists( WP_CONTENT_DIR . '/KIA-comment-blacklist.txt') ? file_get_contents( 'KIA-comment-blacklist.txt', true ) : ''; 
    update_option( 'blacklist_keys', $blacklist ); } 

    // Don't Organize Uploads by Date 

    // Update Permalinks 
    update_option( 'selection','custom' ); 
    update_option( 'permalink_structure','/%post_id%/%postname%/' ); 

    // Create Self as Admin User. 
    // props @David for wp_insert_user() tip 
    $userdata = array(
        'user_login' => 'USERNAME',
        'user_pass' => wp_generate_password(),
        'user_email' => 'YOU@YOUREMAIL.COM',
        'first_name' => 'USER',
        'last_name' => 'NAME',
        'nickname' => 'User',
        'display_name' => 'User',
        'user_url' => '',
        'role' => 'administrator' 

    $self_id = username_exists( $username ); 

    if ( ! $self_id ) { 
        $self_id = wp_insert_user( $userdata );
        update_user_option( $self_id, 'default_password_nag', true, true ); 
        wp_new_user_notification( $self_id, $userdata['password'] ); 


I don’t keep this file live on the server, but I suppose you could if you dropped an .htaccess file into the /wp-content folder and banned access to your custom install.php file

<Files install.php> Order Allow,Deny Deny from all </Files> 

That about sums it all up.  Let me know what cool jedi tricks you are doing with your custom install.php file!


Download the whole thing from my Customizing the WordPress install gist.

16 thoughts on “Customizing the WordPress Install Process with install.php

  1. Leslie Straw says:

    Awesome indeed. We just ordered Thrice Cooked Bacon from Mission Chinese here in San Francisco. Rice cakes, bitter melon, tofu skin, scallion, black bean and chili oil. Lots of chili oil. Its super good. If you ever come here we will treat you, for all your good work. :)

  2. Raymond says:

    This is something I discovered not too long ago. It really comes in handy for those of us that install a lot of WordPress sites with the same settings.

    I created a tool at that will download and enable a theme and plugins as well during the install. The install.php file could be edited more to add the customizations described in this blog post.

    Thanks for sharing your code!

  3. kathy says:

    @Leslie thrice-cooked bacon? swoon? i don’t really eat a lot of bacon, but it is very delicious, makes vegetables taste better and is the perfect dummy content for WordPress testing. It’s a triple threat.

    @Raymond nice work! i don’t actually do this ‘that’ often, but still… it is annoying to do the same things to every site.

    @Julien that’s pretty cool. i’ll have to check it out even though i usually think command line is too geeky even for me. ;)

  4. lin0r says:

    hi kathy!
    thanx for this summary! do you know they are providing an automatically generated index.php to include extensions (include means download actual version, install, activate)! but they don’t offer a possibility to add your own settings, as you did here.
    do you see any chance to include that function (include standard-extensions you’re using for every WP-install) into your index.php? i’m not really good in php… :(
    that would be very helpful!!
    thanx a lot for an anwser and lots of greetings from munich, bavaria, germany!

    • kathy says:

      @lin0r is offering something a little different. They are bundling the plugin files into the WordPress .zip file (if I understand correctly). I’m only customizing options with a custom install.php. Though if you add a custom install.php to their custom .zip file you can probably achieve what you are looking for…. I think you could activate a plugin from the install.php, I’ve never tried. Let me know if you figure it out. thanks for stopping by.

      • lin0r says:

        hi again :)
        thanks a lot for your answer, and sorry for bothering you again… but what they do is just generating an “install.php” file which is doing the rest. the instruction there are:

        – Choose plugins and theme
        – Create pkg file (pkg-file is really just the install.php – i tried that)
        – Place file in ‘wp-content/’
        – Run WordPress install as usual (wp-admin/install.php)
        Selected theme & plugins are automatically downloaded, unzipped, and activated during WordPress installation.

        i guess thats exactly what you’re doing for the custom settings. that’s why i thought you could help me with that, if you look at the output-install.php-file – if thats not too much trouble for you. would be a gr8 help for me and a perfect addition for your post. i would love to do it myself, but my knowledge of php is really poor :-)

        • kathy says:

          @Linor – I’m not bothered in the least. It would be a cool addition, but I just can’t provide free support in the comments. Sorry. If you’re interested in hiring me, please send an email via my contact page.

  5. David says:

    Hi Kathy,

    This is a handy little snippet, particularly the creating an administrator user.

    I was hoping to be able to drill down into the creating myself as an admin user just a little more…

    How would I go about setting the additional user details using install.php

    First Name / Last Name / Nick Name / Display Name / Password (e.g. Cakes123)

    Thanks in advance

    • kathy says:

      David, please take a look at the updated post. Where to set your own password should now be more obvious. First name, etc could probably all be added via update_user_option(). Good luck.

      • David says:

        Hi Kathy! Thanks for your reply. I’ve opted for a different route for a little more control over all the user settings that is set on the auto-magic user creation

        /* Create user automagically */
        $userdata = array(
            'user_login' => 'joepublic',
            'user_pass' => wp_generate_password(),
            'user_email' => '',
            'first_name' => 'Joe',
            'last_name' => 'Public',
            'nickname' => 'Joe',
            'display_name' => 'Joe',
            'user_url' => '',
            'role' => 'administrator' 
        $user_id = wp_insert_user( $userdata );

What do you think?

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    Markdown is turned off in code blocks:
     [This is not a link](

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see

You may use markdown syntax in the comments.