Installing WordPress via Composer

In this article, we’ll look at how to install WordPress via Composer, lock the core version, and not store its files in Git.

We’ll consider two options:

  • the classic structure, when the WordPress core files are copied to the project root;
  • installing the WordPress core in a separate directory, for example wp.

Why install WordPress via Composer

Installing WordPress via Composer makes sense when the site is developed as a version-controlled project with a managed deployment process:

  • the code is stored in Git;
  • updates via the admin panel are disabled;
  • the core, plugins, and other dependencies are installed during the build;
  • changes are first checked and then deployed to the server.

In this kind of project, WordPress becomes a regular dependency rather than part of the source code.

This provides several advantages:

  • the core version is locked in composer.lock;
  • the local environment, staging, and production get the same WordPress version;
  • the project can be restored with the composer install command;
  • WordPress Core files are not stored in Git and do not end up in changes;
  • the repository stays smaller and cleaner.

If the site is updated via the admin panel, does not use Git, and does not use a build and deploy process, installing WordPress via Composer is usually not necessary.

Composer-based WordPress repository

In the examples, we will use the Composer repository doiftrue/wordpress-composer-repo. It allows you to connect the WordPress core as a Composer dependency and downloads the official WordPress ZIP archives.

The repository includes three distribution options:

  • full - the full WordPress, including akismet and hello.php;
  • new-bundled - WordPress without akismet and hello.php;
  • no-content - WordPress without the wp-content directory.

For most projects, it’s more convenient to use:

  • new-bundled, if WordPress is copied to the project root;
  • no-content, if the WordPress core is installed in a separate directory ./wp.

repositories in composer.json is the list of sources for searching packages.

There is no official WordPress Composer package on Packagist. Therefore, you need to add a separate source to composer.json:

"repositories": [
	{
		"type": "composer",
		"url": "https://raw.githubusercontent.com/doiftrue/wordpress-composer-repo/main/repo/new-bundled"
	}
],

See alternative package options for installing WordPress via Composer in the section Alternative.

Option 1 - installing WordPress in the project root

In the classic structure, WordPress core files are located in the project root:

.
├── vendor/
├── wp-admin/
├── wp-content/
├── wp-includes/
├── index.php
├── composer.json
├── composer.lock
├── wp-config.php
├── wp-load.php
└── wp-login.php

The classic structure is better suited for existing sites where WordPress is already installed in the project root. This Composer-based installation does not change WordPress’s usual paths.

Pros:

  • maximum compatibility with plugins;
  • no need to change paths and URLs via constants;
  • easier to integrate into an existing project.

Cons:

  • WordPress core files are mixed together with the project’s files.

composer.json

For the classic structure, use the new-bundled repository and copy the core files from vendor/wordpress/wordpress to the project root via post-autoload-dump.

{
	"repositories": [
		{
			"type": "composer",
			"url": "https://raw.githubusercontent.com/doiftrue/wordpress-composer-repo/main/repo/new-bundled"
		}
	],
	"require": {
		"wordpress/wordpress": "~6.3.0"
	},
	"scripts": {
		"post-autoload-dump": "rsync -a --exclude={wp-content/,wp-config-sample.php} ./vendor/wordpress/wordpress/* ./"
	}
}

The post-autoload-dump script runs automatically after the following commands:

composer install
composer update

After installation, WordPress will first appear in the directory:

vendor/wordpress/wordpress

Then rsync will copy the WordPress Core files to the project root, excluding wp-content and wp-config-sample.php.

For this option, rsync must be available on the server.

.gitignore

To prevent the core files from ending up in Git, you can use the following .gitignore:

/vendor/

/index.php
/license.txt
/readme.html
/wp-activate.php
/wp-blog-header.php
/wp-comments-post.php
/wp-config-sample.php
/wp-config.php
/wp-cron.php
/wp-links-opml.php
/wp-load.php
/wp-login.php
/wp-mail.php
/wp-settings.php
/wp-signup.php
/wp-trackback.php
/xmlrpc.php

/wp-admin/
/wp-includes/

/wp-content/*
!/wp-content/

/wp-content/plugins/*
!/wp-content/plugins/
!/wp-content/plugins/YourCustomPluginName/

/wp-content/themes/*
!/wp-content/themes/
!/wp-content/themes/YourCustomTheme/

If you need to add a new theme or plugin to the repository, add an exception:

!/wp-content/plugins/my-plugin/
!/wp-content/themes/my-plugin/

The symbol ! отменяет правило игнорирования для конкретной директории.

Option 2 - installing WordPress in a separate directory

In this option, the WordPress core is installed in a separate wp directory, and wp-content stays in the project root:

.
├── index.php
├── composer.json
├── composer.lock
├── vendor/
├── wp/              # WordPress Core
├── wp-config.php
└── wp-content/
	├── mu-plugins/
	├── plugins/
	├── themes/
	└── uploads/

This option is better suited for new projects.

Pros:

  • core files are separated from the project code.
  • easier to work with themes, plugins, and wp-content.
  • less risk of accidentally touching project files when updating WordPress.

Cons:

  • you need to configure index.php.
  • you need to set path and URL constants in wp-config.php.
  • old or poorly written plugins may expect the standard file structure.

In such a structure, the admin panel will be available at:

https://example.com/wp/wp-admin

composer.json

To install WordPress in a separate directory, use the no-content repository and the composer/installers package.

The no-content option contains WordPress Core files without the wp-content directory.

{
	"repositories": [
		{
			"type": "composer",
			"url": "https://raw.githubusercontent.com/doiftrue/wordpress-composer-repo/main/repo/no-content"
		}
	],
	"require": {
		"wordpress/wordpress": "~6.3.0",
		"composer/installers": "*"
	},
	"extra": {
		"installer-paths": {
			"wp/": [
				"wordpress/wordpress"
			]
		}
	},
	"config": {
		"allow-plugins": {
			"composer/installers": true
		}
	}
}

After that, run:

composer install

Composer will install WordPress into the wp/ directory specified in extra.installer-paths for the wordpress/wordpress package.

.gitignore

/vendor/
/wp/

/wp-content/*
!/wp-content/
!/wp-content/index.php

!/wp-content/mu-plugins/
!/wp-content/plugins/
!/wp-content/themes/

If you need to add a specific directory from wp-content to Git, add an exception:

!/wp-content/plugins/YourCustomPluginName/
!/wp-content/themes/YourCustomTheme/

The symbol ! отменяет правило игнорирования для конкретной директории.

index.php

The WordPress entry point is the index.php file, so you need to create it in the project root.

Create index.php with this code:

<?php

define( 'WP_USE_THEMES', true );

require __DIR__ . '/wp/wp-blog-header.php';

This is a standard index.php, with only the path to wp-blog-header.php changed—it points to the wp directory.

wp-content

The WordPress core is located in the wp directory, and themes, plugins, and uploads will be stored in the root wp-content directory.

Create the wp-content directory in the project root:

wp-content/
├── index.php
├── mu-plugins/
├── plugins/
├── themes/
└── uploads/

The project’s themes and plugins will be stored here, not inside ./wp/wp-content.

wp-config.php

To make WordPress find the core in the wp directory and the root wp-content directory, you need to configure wp-config.php.

Create wp-config.php in the project root and add the following constants to it:

define( 'WP_HOME', 'https://' . $_SERVER['HTTP_HOST'] );
define( 'WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST'] . '/wp' );

define( 'WP_CONTENT_DIR', __DIR__ . '/wp-content' );
define( 'WP_CONTENT_URL', WP_HOME . '/wp-content' );

WP_HOME is the front-end URL of the site.

WP_SITEURL is the URL of the WordPress core directory.

These constants override the home and siteurl option values from the wp_options table, but do not change them in the DB.

WP_CONTENT_DIR and WP_CONTENT_URL tell WordPress where the wp-content directory is located.

WordPress version in Composer

Allowed WordPress versions are specified in composer.json using version constraints.

The exact version without switching to other releases:

"wordpress/wordpress": "6.3.0"

Updates within the 6.3 branch, for example from 6.3.0 to 6.3.1:

"wordpress/wordpress": "~6.3.0"

Updates to any stable versions within 6.x:

"wordpress/wordpress": "^6.3.0"

The latest development version of WordPress:

"wordpress/wordpress": "dev-master"

dev-master is intended for development and testing, not for production sites.

To update only WordPress within the specified constraint, run:

composer update wordpress/wordpress

The selected version will be written to composer.lock. After that, composer install will install the locked WordPress version on all environments.

Alternative - roots/wordpress

The article uses the Composer repository doiftrue/wordpress-composer-repo. An alternative option is the roots/wordpress package, available via Packagist.

Both projects are not official WordPress packages.

doiftrue/wordpress-composer-repo roots/wordpress
Source Separate Composer repository Packagist
Distributions full, new-bundled, no-content no-content, separate package roots/wordpress-full
Installation composer/installers or Composer script roots/wordpress-core-installer
Project structure You can install the core in the root or in a separate directory Primarily designed for a separate core directory

roots/wordpress is a meta-package from the Roots ecosystem. It connects roots/wordpress-no-content and the roots/wordpress-core-installer installer. For the full distribution, there is a separate package roots/wordpress-full.

doiftrue/wordpress-composer-repo is more convenient if you need your own project structure or want to install WordPress in the root. roots/wordpress is better suited for Roots projects or projects with a separate core directory.