Software

What Is a Git Submodule, and How Do You Use It?

Talha Aslan 16 min read 1 views

What Is a Git Submodule?

In practice, a git submodule links one Git repository inside another as a dependency. The parent project does not copy the submodule's files into its own history. Specifically, it stores a pointer to one specific commit of that other repository, recorded in a .gitmodules file and a special directory entry.

Anyone cloning the parent project sees the submodule's path and the commit it expects, but that commit's files arrive through a separate step. This separation looks odd at first, yet it is exactly how two independent histories stay distinct instead of blending together. Instead, the submodule keeps its own branches, tags, and contribution flow, fully apart from the parent.

What Does the .gitmodules File Store?

.gitmodules is a plain text file that maps each git submodule's local path to its remote URL. Teams commit this file alongside the parent project, so anyone who clones the repository sees exactly which submodule comes from which address. Still, you can edit it by hand, but we recommend letting the git submodule commands generate it for you.

  • Path: the submodule's folder name inside the parent project.
  • Url: the remote address the submodule clones from.
  • Branch (optional): the branch name tracked during updates.

For the exact behavior of every command, the official Git Tools: Submodules documentation is the most reliable source, and it is what we checked against while preparing this guide. In a project with several submodules, this file gives you a complete dependency map at a glance. Nested submodules, meaning a submodule that itself contains another submodule, are also possible. The --recursive flag then handles each layer in order.

When Should You Use a Git Submodule?

Specifically, a git submodule makes sense when a component needs its own history but you still work on it directly. Say your team shares a UI library across several projects and occasionally contributes fixes back to it. In that case, a submodule fits well. For a dependency you only consume and never touch, a package manager is usually the simpler route.

For example, picture a team that reuses the same validation rules across multiple web projects. The rules live in their own repository, each project pulls that repository in as a submodule, and the update step runs whenever it is needed. This setup manages the rules from one place while letting each project update on its own schedule.

How Is a Git Submodule Different from a Package Manager?

For instance, a package manager, such as npm, Composer, or pip, downloads a dependency by version number. As a result, you generally never touch its source. A git submodule brings a full Git repository instead. So the submodule keeps its own branches, commit history, and room to edit. Instead, pick a package manager when you only consume code. Pick a git submodule when you plan to develop alongside it.

FeatureGit SubmodulePackage Manager
Source accessFull repository, editableUsually prebuilt or read-only
Version trackingPinned to a specific commitPinned to a version number
Update stepCommit pointer updated manuallyDependency file updated
Team learning curveSteeper, extra commands neededFlatter, already familiar

That contrast shapes daily workflow too. In practice, a team on a package manager bumps a version number and moves on. A team on submodules has to manage both the parent's pointer and the submodule's own commit at the same time, which is the trade-off for the extra flexibility.

How Do You Add a Git Submodule?

To add a git submodule, you first go to the root of the parent project and run git submodule add <repo-url>. Add a path argument if you want a different folder name, for example git submodule add <repo-url> libs/ui. The command creates or updates .gitmodules, which you then commit along with your other changes.

  1. Go to the root of the parent project.
  2. Run git submodule add with the remote URL.
  3. Stage the new .gitmodules file and the new folder with git add.
  4. Commit the change with a clear message.

After that, you will notice a new folder once the addition finishes. It behaves as a special Git entry rather than a plain directory. Then step inside it and check its status, and you see that submodule's own state, not the parent's.

Rather than adding each dependency one at a time and repeating the same commands in every folder, git submodule foreach runs one command across every submodule in sequence. That saves time on a project with many dependencies and spares you from visiting each folder by hand.

How Do You Clone a Repository That Contains Submodules?

Specifically, a plain git clone leaves submodule folders empty, since the main clone step does not fetch a submodule's content automatically. Use git clone --recurse-submodules <url> instead, and everything arrives on the first pass, including nested submodules. If you already cloned the standard way, git submodule update --init --recursive finishes the same job afterward.

In practice, this step can take a moment on a large repository, since each submodule downloads with its full history. Teams on a slow connection who only need a partial history have shallow-clone options for that, though that is a separate topic we will not cover here.

Why Does a Submodule Folder Show Up Empty?

In practice, this is the complaint teams raise most often: someone clones the repository, opens the submodule folder, and finds it empty. The reason is simple. Either the --recurse-submodules flag was skipped during cloning, or the init and update steps were never run afterward. The fix is git submodule update --init --recursive, which sets up the submodule configuration first and then fetches the pinned commit.

Therefore, teams who want to prevent this for good write the correct clone command into the README. They also add the same check to their setup script. That way a new developer never has to remember an extra manual step.

How Do You Pull Submodule Updates?

However, updating the parent project can change the submodule's pointer, but that alone does not refresh the files inside the submodule folder. Run git submodule update --init --recursive to move to the commit the parent project now records. From Git 2.14 onward, set git config submodule.recurse true once, and a plain git pull will update submodules automatically.

As a result, setting that option globally removes a repeated mistake for the entire team. On an older machine without that setting, you will still need to check the status after a pull and run the update command by hand once you notice the submodule changed.

How Do You Bring New Upstream Commits into a Submodule?

Specifically, when you want the submodule's own branch at its newest state, rather than whatever the parent project points at, use git submodule update --remote. This fetches the latest commit on the branch defined in .gitmodules. Add the submodule's path to update only one of them. Remember to commit the new pointer in the parent afterward, or your teammates stay on the old version.

To pin which branch gets tracked, write it into the file with git config -f .gitmodules submodule.<name>.branch <branch>. That way the --remote flag always knows exactly which branch to pull from. The whole team gets the same result.

What Is the Detached HEAD Problem Inside a Submodule?

Specifically, the git submodule update command checks out the submodule at a specific commit in a detached HEAD state, meaning it is not attached to any branch. Commit directly in that state, and the next update can leave those commits unreachable. Run git checkout <branch> before working inside the submodule, and whatever you commit stays safely tracked.

Still, a developer who commits while detached without noticing should not panic. Git does not delete those commits right away. They simply become harder to reach without a branch pointing at them. The git reflog command can locate the commit's identity in that case, and moving it onto a branch recovers it.

What Should You Check Before Pushing Submodule Changes?

First, make sure the submodule's own commits reached the remote before you push the parent project. Otherwise another developer can pull the parent and still fail to find the commit the submodule points at. Automate this check with git push --recurse-submodules=check, which stops the push if a submodule commit has not gone up yet. Alternatively, git push --recurse-submodules=on-demand pushes submodules first and then the parent project automatically.

Make either flag the team default with git config push.recurseSubmodules on-demand. Nobody has to remember the flag by hand, and every push proceeds in the safe order.

Why Does the Submodule Reference Break After Switching Branches?

Specifically, different branches can point to different submodule commits. Switching branches does not automatically refresh the submodule's content, so the folder appears stuck on the old commit. From Git 2.13 onward, git checkout --recurse-submodules <branch> moves submodules to the right commit as part of the branch switch itself. To fix this across the whole team, we recommend every developer set git config submodule.recurse true.

Without that setting, teams often see git status mark the submodule as changed right after switching branches. That usually is not corruption. It just means the folder has not yet been updated to match the new branch's pointer. A single update command resolves it.

How Do You Resolve a Submodule Pointer Conflict?

For example, if two developers pin different commits of the same submodule on diverging branches, Git cannot merge that conflict on its own. The git diff output shows two separate commit identifiers. Go into the submodule, decide which commit is correct, merge the two if needed to create a new commit, then return to the parent project and stage that submodule path with git add to close the conflict. A similar merging logic applies to ordinary file conflicts too. See our guide on how to resolve git merge conflicts for that broader topic.

This kind of conflict usually surfaces when two developers update the same dependency at the same time for different reasons. A short conversation about which change takes priority, held before you resolve the conflict, makes the merge itself much easier.

How Do You Convert an Existing Folder into a Submodule?

In practice, Git will not let you turn a folder the parent project already tracks directly into a submodule, since two tracking mechanisms would collide on the same path. Remove the folder from the parent's tracking first with git rm -r <folder>, then run git submodule add on that same path. Back up the folder before this step to avoid losing any data.

If you want to carry the folder's old commit history into the new submodule, that is a separate history-merging operation and needs careful planning. In practice, most teams find it easier to start a fresh submodule repository than to untangle that kind of history transplant.

What Happens if a Submodule's URL Changes?

If the address hosting the submodule changes, update the url line in .gitmodules first. Then run git submodule sync --recursive to bring the local configuration in line with the new address. Finally, run git submodule update --init --recursive so every developer's machine pulls content from the correct source.

However, editing only the .gitmodules file is not enough on its own. The local configuration still holds the old address, and the update can fail quietly. The sync step is exactly what refreshes that stale record.

How Does a Git Submodule Differ from a Monorepo?

Put simply, a git submodule links repositories with independent histories through a loose reference. Each project still lives in its own repository. A monorepo does the opposite, keeping many projects inside one repository with one shared commit history. Submodules suit components that are versioned independently but shared across projects. A monorepo fits teams who want to see and change all their code together. Our article on what a monorepo is, with its pros and cons weighs that choice in more depth.

Some teams combine both: the core product stays in a monorepo, while a rarely changing component shared with other projects gets added as a submodule. This mix lets each approach handle the part it is best suited for.

What Should a Team Watch for When Using Submodules?

In practice, a team working with a git submodule benefits from a shared set of rules. Document the --recurse-submodules flag in the clone instructions and spell it out in the README. Make switching to a branch before committing inside the submodule a standing habit. Check that your continuous integration pipeline also runs the submodule init and update steps. Otherwise builds will keep running against stale code.

  • Document clone commands with --recurse-submodules.
  • Require the submodule.recurse setting across the team.
  • Build the habit of switching to a branch before working in the submodule.
  • Add the submodule update --init --recursive step to CI scripts.

Writing these rules into the project documentation, rather than leaving them as a verbal agreement, stops a new team member from repeating the same mistakes. A short setup checklist heads off a lot of accumulated confusion over time.

What Are the Common Pitfalls of Using a Git Submodule?

The most common pitfall is an outdated reference going unnoticed. You pull the parent project, but the submodule folder stays on an old commit because the update step got skipped. The second common mistake is forgetting the --recurse-submodules flag during cloning, which leaves the folder completely empty. The third pitfall is committing directly inside the submodule while in detached HEAD state, then losing those commits. Pushing the parent project before pushing the submodule's own changes is the fourth. So another developer pulling the parent still cannot find the commit it needs.

All four pitfalls share one root cause: overlooking the submodule's two-layer nature. The parent project and the submodule are two states that need managing separately, and making sure one is updated whenever the other is forms the core discipline of working with submodules.

What Are the Alternatives to a Git Submodule?

A git submodule is not the only option. Git subtree merges the child project's files directly into the parent's history and skips a separate clone step. That comes at the cost of a more tangled history. Package managers such as npm, Composer, or pip offer a simpler path for dependencies you only consume and never modify. A monorepo approach removes separate repositories entirely and gathers everything into one. The right choice depends on how often you touch the dependency and on your team's habits.

A new project's simplest path is often avoiding submodules unless they are genuinely needed. The flexibility a submodule offers comes with the burden of remembering extra commands in the right order, a burden that should be weighed against how often you actually touch the dependency.

Deciding early in a new project prevents the extra work of switching methods later. Keeping things simple while the team is small, then picking the right tool once the need is clear as the team grows, is usually the healthiest path.

What Should You Watch for When Using an Open Source Dependency as a Submodule?

When you add an open source library as a git submodule, check its license and contribution guidelines first. If you plan to send fixes upstream, working from your own fork and pointing the submodule's url field at that fork is a practical approach. Our guide on how to contribute to open source on GitHub walks through that process in more depth.

Working from a fork means you need to pull upstream updates into it from time to time. Otherwise your submodule drifts further from the real project. Making that sync a regular habit prevents a large gap from building up later.

What Happens If a .env File Inside a Submodule Gets Committed by Mistake?

Since a submodule is its own repository, its own .gitignore rules apply. The parent project's rules do not cover it. If a .env file gets committed inside the submodule by mistake, the leak sits in that submodule's own history, and cleaning the parent project is not enough. Our guide on fixing an env file accidentally committed to git applies in that case directly to the submodule's own repository.

That is why every submodule needs its own .gitignore prepared with the same care as the parent's. One of the first things to check when adding a new submodule is whether sensitive files are correctly excluded there too.

Which Development Tools Work Well Alongside a Git Submodule?

If you want your whole team running a submodule-based project the same way, standardizing the development environment helps. A container-based setup can automate the submodule clone and update steps. We cover that approach in our article on what a VS Code dev container is. Code formatting tools also need the same rules across every submodule to stay consistent. Our piece on fixing an ESLint and Prettier conflict is a useful reference there.

Do not forget install scripts either. Some teams run pnpm install inside the submodule right after cloning it. Adding that step to the setup script cuts down the number of manual actions a new developer has to remember.

Who Sets Up a Git Submodule Structure Correctly?

At Talha Aslan and team, we often meet custom software projects where several components live in separate repositories but get developed under one shared roof. In those cases we set up the git submodule structure and prepare the team's internal documentation. If your project needs that kind of setup, take a look at our custom software development service. A correctly built submodule structure keeps the team from running into confusion later.

What we watch for during setup is whether the technical choice fits the team's day-to-day workflow. Sometimes a simpler solution beats a submodule. Making that call starts with understanding how often the team actually touches each component.

Weighing this early, together, and writing the decision down gives the team a ready answer to "why was this set up this way" once it grows. A short note can save a lot of time months later.

How Do You Keep Git Submodule Commands in Mind?

Most submodule commands extend the git submodule subcommand: add, init, update, sync, foreach. You do not need to memorize them. What matters is recognizing which situation calls for which command. Our Git and GitHub guide covering essential commands recaps the basic steps that come before submodules.

One tip: bind the commands you use often to short aliases, which speeds up daily work. Tying a long update command to a short alias means you never have to type the right flags from memory. It also cuts down on mistakes.

Finally, a small exercise helps when introducing a git submodule structure to the team: have a new developer clone the repository, run an update, then make a deliberate small change inside the submodule and undo it. That short drill shows how the commands behave in practice rather than on paper, and it takes the panic out of the first real issue.

Frequently Asked Questions

What is a git submodule?
A git submodule links one Git repository inside another as a dependency. The parent project does not copy the submodule's files; it stores a pointer to one specific commit, kept in a .gitmodules file and a special directory entry. The submodule keeps its own commit history, branches, and URL, living independently of the parent project.
How do you clone a repository that contains submodules?
A plain git clone leaves submodule folders empty because it does not fetch submodule content automatically. Use git clone --recurse-submodules instead. If you already cloned the standard way, git submodule update --init --recursive finishes the job afterward and also pulls in any nested submodules.
Why does a submodule folder show up empty?
This usually happens because the --recurse-submodules flag was skipped during cloning. When the init and update steps never run afterward, Git leaves only the empty directory entry. The fix is git submodule update --init --recursive, which sets up the configuration and fetches the pinned commit, filling the folder.
How does a git submodule differ from a monorepo?
A git submodule links repositories with independent histories through a loose reference, and each project stays in its own repository. A monorepo keeps many projects inside one repository with a single shared commit history. Submodules suit independently versioned components, while a monorepo fits teams that want to see and change all their code together.
What is the detached HEAD problem inside a submodule?
The git submodule update command checks out the submodule at a specific commit without attaching it to any branch, known as a detached HEAD. Commit directly in that state, and a later update can leave those commits unreachable. Running git checkout to a branch first keeps whatever you commit safely tracked.
What are the alternatives to a git submodule?
Git subtree merges the child project's files directly into the parent's history and skips a separate clone step. Package managers such as npm or Composer are simpler for dependencies you only consume. A monorepo removes separate repositories entirely. The right choice depends on how often you touch the dependency.
  • git submodule
  • git
  • github
  • version control
  • software development
  • monorepo
Share:
Talha Aslan

Google Partner digital marketing expert. Hands-on with SEO, Google Ads, web design and e-commerce projects since 2012; every post here comes from that experience.

Next project

Get a quote for your project.

Your brief goes straight to Talha Aslan and team: strategy led by Talha, delivery by an experienced team. We reply the same day and send the scope, timeline and price as a written quote within 1 business day.