Git Submodules Explained With Practical Examples

A Git submodule lets one repository include another repository at a specific commit. It can be useful for shared libraries, themes, or separately maintained components, but its extra repository boundary means a normal clone or commit does not behave quite like a regular folder. This guide explains the model and walks through common submodule workflows.

Practice Git workflows →

What is a Git submodule?

A submodule is a separate Git repository checked out inside a parent repository. The parent records the submodule's repository URL in .gitmodules and stores a reference to one specific commit, often called a gitlink. It does not record every file in the submodule as ordinary parent-repository files.

That pointer makes builds reproducible: a parent commit selects a known submodule commit rather than automatically taking whatever is newest. The trade-off is that collaborators must initialize the submodule, and a change inside it and a change to its parent pointer are separate commits in separate repositories.

How to add a submodule to a repository

From the root of your parent repository, add the submodule with its repository URL and the path where you want it checked out:

git submodule add https://github.com/OWNER/REPOSITORY.git external/example

Replace the example URL and path with the actual repository and desired location. Git creates or updates .gitmodules, checks out the repository under external/example, and records its current commit in the parent repository.

Review and commit both the configuration and the pointer from the parent repository:

git status
git add .gitmodules external/example
git commit -m "Add example submodule"

How to clone a repository that has submodules

The simplest way to clone a parent repository and initialize all nested submodules is:

git clone --recurse-submodules https://github.com/OWNER/PROJECT.git

If you already cloned without that option, initialize the configured submodules afterward:

git submodule update --init --recursive

Initialization fetches and checks out the commits recorded by the parent. A submodule may appear on a detached HEAD; this is normal when the parent pins a commit rather than a moving branch. Avoid committing new work there unless you deliberately create or switch to a branch in the submodule first.

How to update a submodule and the parent pointer

To move a submodule to a newer commit, first work inside the submodule. For example, fetch and fast-forward its main branch if that is the branch the project intends to follow:

git -C external/example fetch origin
git -C external/example switch main
git -C external/example pull --ff-only

If the submodule has no local main branch, create one that tracks the remote branch with git -C external/example switch --track -c main origin/main. Use the branch name your dependency actually maintains.

Check the submodule's status and test the parent project. Then return to the parent repository and commit the new pointer:

git status
git add external/example
git commit -m "Update example submodule"

The parent commit records the selected submodule commit, not a promise to follow the branch automatically. Teammates receive the new pointer when they pull the parent and run git submodule update --init --recursive.

Common Git submodule issues

Git submodules FAQ

Do submodules automatically track the latest commit?

No. The parent repository records a specific submodule commit. Updating a submodule changes the pointer, and that change must be reviewed and committed in the parent.

Are submodules the same as copying a folder?

No. The submodule keeps its own Git history and remote, while the parent stores a commit reference and configuration. If you need a single repository with all files and history together, consider whether a monorepo or another dependency-management approach better fits the project.

Learn Git Faster by Practicing

Reading is just the first step. Real learning happens when you practice.

Start Learning Git →