Skip to content
ThemesIonic — home
WordPress Tutorials

WordPress and Git

Git tracks files. WordPress keeps its content in a database and its uploads on disk, so the useful question is not whether to version a WordPress site but which parts of it to version.

4 min read advanced

Git tracks files; WordPress keeps most of what matters somewhere else. Content is in the database, uploads are on disk, and settings are rows in the options table. So "put WordPress in Git" is the wrong instruction. The right one is: version the code that defines the site, and manage everything else with tools built for it.

Get that division right and version control makes a WordPress project dramatically easier to work on. Get it wrong and you have a repository full of noise that nobody trusts.

What to track, and what not to

Track Do not track
Your own theme, including a child theme WordPress core
Plugins you wrote Third-party plugins (manage as dependencies)
composer.json / lock file wp-config.php with real credentials
Build config, CI config, deploy scripts wp-content/uploads/
A committed .gitignore Cache and log directories
Documentation and setup notes Anything containing a secret

A workable .gitignore for a project that manages dependencies with Composer:

/wp-admin/
/wp-includes/
/wp-*.php
!/wp-config-sample.php
/index.php
/license.txt
/readme.html
/wp-content/uploads/
/wp-content/upgrade/
/wp-content/cache/
/wp-content/plugins/*
!/wp-content/plugins/my-site-plugin/
/wp-content/themes/*
!/wp-content/themes/my-theme/
wp-config.php
*.log
.DS_Store

The * then ! pattern is the important idiom: ignore everything in a directory, then explicitly re-include the things you wrote.

Core and plugins as dependencies

Committing third-party code means every plugin update is a large, unreviewable diff, and your repository slowly becomes a mirror of someone else's release history.

The alternative is to declare them. Composer can install WordPress core and plugins from the public directory, so composer.json becomes the statement of what the site is made of, and the lock file pins exact versions:

{
  "require": {
    "johnpbloch/wordpress": "^6.7",
    "wpackagist-plugin/wordpress-seo": "^24.0"
  },
  "repositories": [
    { "type": "composer", "url": "https://wpackagist.org" }
  ]
}

This gives you reproducible environments and a readable history of dependency changes. It costs you the ability to update plugins from the WordPress admin — updates now happen in the repository and deploy from there. That is a real workflow change, and it is the main reason teams adopt it and solo site owners often should not.

If that is too much, the middle path is legitimate: track only your own theme and site-specific plugin, and keep updating everything else through the admin as usual, following how to update WordPress plugins safely.

The database stays out

There is no useful way to version a WordPress database. It changes on every page view, a dump produces diffs nobody can read, and merging two of them is not a thing.

What to do instead:

  • Content moves down, never up. Pull production content to staging and local, not the reverse. Pushing a database up destroys everything written on production since the copy — orders, comments, form entries.
  • Rewrite URLs when you move a database between environments, using a serialisation-aware tool: wp search-replace, from WP-CLI.
  • Configuration that must be versioned — post types, taxonomies, settings a plugin stores in options — belongs in code where possible. Custom post types registered in a site-specific plugin are in the repository; the same types created through a UI plugin are database rows and are invisible to Git.
  • Backups remain a separate obligationhow to back up a WordPress site.

Secrets

wp-config.php holds database credentials and salts. It must not be in the repository.

Commit an example instead — wp-config-sample.php or a .env.example — listing the keys with no values, and load real values from the environment or from an untracked file per environment. If credentials were ever committed, rotate them: removing the file in a later commit does not remove it from history.

What belongs in the config file at all is covered in wp-config.php explained.

Deploying from a repository

Three approaches, in increasing order of discipline:

  1. Pull on the server. A git pull over SSH, run by hand or by a small script. Simple; easy to forget a step.
  2. A build and deploy pipeline. CI installs dependencies, builds assets, runs tests, and pushes the result. The repository holds source; the server receives a built artifact.
  3. A host with built-in Git deployment, which many managed hosts provide — see managed WordPress hosting.

Whichever you use, the deploy should also handle the steps a file copy does not: clearing caches, flushing rewrite rules after registering new post types, and running any migration. Never deploy straight to production without the same change having run on staging first.

Common mistakes

  • Committing wp-content/uploads/, which makes the repository enormous and still is not a media backup.
  • Committing wp-config.php with live credentials.
  • Committing core and every plugin, then losing the ability to review any diff.
  • Treating Git as a backup. It has no content, no orders and no images.
  • Editing files directly on production, so the server and the repository disagree and the next deploy silently reverts a fix.
  • Pushing a database upward and destroying live data.
  • No .gitignore until later, leaving secrets in the history.

Verify

Clone the repository into an empty directory, install dependencies, supply a config and a database, and confirm you can bring the site up from nothing but the repository plus a content dump. If you cannot, something the site depends on is not tracked — and finding out now is considerably cheaper than finding out during a recovery.

Frequently asked

Usually not. Track your own themes and plugins and the configuration that defines the site. Core and third-party plugins are better managed by a dependency tool, and uploads do not belong in a repository at all.
Not usefully. It changes on every visit and every content edit, and a database dump produces meaningless diffs. Content moves through exports and migration tooling, not through Git.
Yes, and they are different things. Git protects code. Your content, orders and uploads live in the database and the uploads folder, neither of which is in the repository.

Related guides