Git Submodule
On this page, you will find a brief introduction to Git submodules, how to pull, push, clone, and update them, and all the needed use cases for submodules.
A submodule lets you embed one Git repository inside another and keep their histories completely separate. This page explains what a submodule is, when it is the right tool, and the full lifecycle of commands: adding, cloning, pulling, updating, pushing, and removing a submodule, plus the common gotchas that make submodules tricky.
What is a Submodule
Very often, a code repository depends on external code from other repositories. You can directly copy and paste the external code into the main repository, or use a language's package management system. However, both methods have the downside of not tracking changes to the external repository.
Git lets you include other Git repositories — called submodules — inside a single repository. A submodule lives at a specific path in the parent repository's working directory and is, itself, a full clone of another repository with its own .git history.
The key idea is that a submodule is pinned to one exact commit, not to a branch or a tag. The parent repository stores only the submodule's path, URL, and the SHA of the commit it expects. This is what lets you depend on external code at a known, reproducible point in time.
Two files track this relationship:
.gitmodules— a tracked file at the root of the parent repository. It maps each submodule's path to its remote URL (and optionally a branch). This file is committed and shared with everyone..git/configand the gitlink entry in the tree — local, per-clone bookkeeping that records the actual checked-out commit (the gitlink shows up with mode160000).
Submodules support adding, synchronizing, updating, and cloning, but because the parent only remembers a commit SHA, updating a submodule is always a deliberate, two-step act — never automatic.
When to Use Submodules
Working with submodules is tricky, so we suggest some best use cases for them.
- If the subproject is changing too fast or upcoming changes will break the API, lock the code to a specific commit for safety.
- If a component isn’t updated very often, and you want to track it as a vendor dependency.
- If you represent a part of the project to a third party, and you want to integrate their work at a particular time (works only when updates are not too frequent).
- If the technological context allows packaging and formal dependency management, you should use package managers instead of submodules.
- If your codebase is massive and you don’t want to fetch it every time, use submodules so collaborators only download the parts they need.
Adding a Submodule
First, create (or move into) the repository that will hold the submodule:
mkdir git-submodule-demo
cd git-submodule-demo/
git initInitialized empty Git repository in /Users/example/git-submodule-demo/.git/Add a submodule with git submodule add, passing the URL of the repository you want to embed:
git submodule add https://somehost/example/textexampleCloning into '/Users/example/git-submodule-demo/textexample'...
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 8 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (8/8), done.Git immediately clones the textexample repository into a folder of the same name and creates a .gitmodules file. To place the submodule at a different path, add it as the last argument, for example git submodule add <url> vendor/textexample.
Now check the state of the repository with git status:
git statusOn branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitmodules
new file: textexampleNotice that textexample is staged as a single entry, not as the individual files inside it. The parent repository only tracks the submodule's commit pointer. Commit both files with git add and git commit:
git add .gitmodules textexample
git commit -m "Add textexample submodule"[master (root-commit) d5002d0] Add textexample submodule
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 textexampleThe mode 160000 is special: it marks textexample as a gitlink (a pointer to a commit) rather than a normal directory.
Checking Submodule Status
Before changing anything, see where each submodule currently stands:
git submodule status a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 textexample (v1.2.0)The leading character is meaningful: a space means the submodule is at the expected commit, a + means it is checked out at a different commit than the parent records, and a - means it is not initialized yet.
Updating Submodules
Team members must update submodule code when it has been modified elsewhere. You cannot rely on git pull alone, because pulling the parent repository only changes the recorded submodule commit — it does not touch the files checked out inside the submodule. To check out the commit the parent expects, run:
git submodule updateWithout the --remote flag, this command checks out the commit recorded in the parent repository and does not fetch new upstream changes.
To instead pull the latest commit from the submodule's tracked branch (main by default, or the branch set in .gitmodules), use --remote:
git submodule update --remote textexampleThis fetches the submodule's upstream and moves it forward. The parent repository now sees a new commit pointer, so you must git add and commit the submodule to record the change.
To run an update across every submodule, including nested ones, add --init --recursive:
git submodule update --init --recursiveIf the .gitmodules file changes (for example, the submodule URL moves), run git submodule sync to copy the new URLs into your local .git/config before updating:
git submodule sync --recursiveCloning Git Submodules
To clone a project with submodules, use the git clone command. By default, it clones the parent repository but leaves submodule directories empty. You must then run git submodule init and git submodule update. The former updates the local .git/config with the mappings from .gitmodules, while the latter fetches the submodule data and checks out the recorded commit.
If you cloned without --recurse-submodules, populate the submodules afterward:
git clone /url/to/repo/with/submodules
git submodule init
git submodule updateThe shortcut git submodule update --init combines those last two commands. Better still, clone everything in one step with --recurse-submodules:
git clone --recurse-submodules /url/to/repo/with/submodulesThis clones the parent and automatically initializes and checks out every submodule, so the working tree is complete immediately.
Pulling the Submodule's Code
When you pull a parent repository that has gained new submodules, those submodules arrive uninitialized. First, fetch the latest parent state:
git pullIf new submodules are listed, initialize and fetch them in one step:
git submodule update --init --recursivegit submodule init on its own only copies the mappings into the local .git/config; it does not download any code. The update step is what actually fetches the submodule and checks out the recorded commit. You can make git pull do this automatically by setting:
git config submodule.recurse truePushing Updates in a Submodule
A submodule is a full, independent Git repository, so you commit and push inside its directory exactly as you would anywhere else:
cd textexample
git checkout main
# ...edit files...
git commit -am "Fix typo in textexample"
git push
cd ..After committing inside the submodule, the parent repository still points to the old commit. Running git status in the parent now shows:
modified: textexample (new commits)Record the new pointer by staging and committing the submodule path in the parent, then pushing:
git add textexample
git commit -m "Bump textexample to latest"
git pushTo avoid pushing the parent before its submodule commits exist on the remote (which would leave teammates with a broken, unreachable pointer), push everything together:
git push --recurse-submodules=on-demandThis pushes any unpushed submodule commits first, then the parent.
Removing a Submodule
Deleting the folder by hand is not enough — Git keeps its bookkeeping. Remove a submodule cleanly with:
git submodule deinit -f textexample
git rm textexample
git commit -m "Remove textexample submodule"deinit unregisters the submodule and removes its working tree, git rm deletes the gitlink and its .gitmodules entry, and the commit records the removal.
Summary
Submodules are a good way to keep projects in separate repositories while still referencing them as folders in another repository's working directory. The mental model to keep is simple: the parent stores a commit pointer, every update is deliberate, and --recurse-submodules saves you from half-populated clones. For frequently changing dependencies, a real package manager is usually the better choice. To go deeper into related workflows, see git clone, git pull, and git status.