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 installcommand; - 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, includingakismetandhello.php;new-bundled- WordPress withoutakismetandhello.php;no-content- WordPress without thewp-contentdirectory.
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.