How to add a submodule in Git
Managing shared code across multiple projects often leads to code duplication and synchronization issues, especially when working with libraries or components used in several repositories. With over 25 years of experience in software development and as the creator of CoreUI, I’ve managed complex multi-repository setups for component libraries, documentation sites, and enterprise applications. From my expertise, Git submodules provide the most reliable solution for including external repositories within your project while maintaining independent version control. This approach allows you to track specific commits of external code, ensuring consistency and reproducibility across your development workflow.
Use git submodule add to include an external repository as a subdirectory in your project.
git submodule add https://github.com/username/repo-name.git path/to/submodule
How It Works
The git submodule add command clones the external repository into the specified path and creates a .gitmodules file that tracks the submodule configuration. Git records the specific commit SHA of the submodule in the parent repository, ensuring that everyone working on the project uses the exact same version of the submodule. The submodule appears as a regular directory in your project but maintains its own Git history independently.
Adding Submodule to Specific Path
Add a submodule to a custom directory within your project:
git submodule add https://github.com/user/shared-lib.git lib/shared
This clones the shared-lib repository into the lib/shared directory. The directory structure is created automatically if it doesn’t exist. After adding the submodule, you’ll see changes in your staging area including the .gitmodules file and the new submodule directory.
Committing the Submodule
After adding a submodule, commit the changes to your repository:
git submodule add https://github.com/user/components.git src/components
git add .gitmodules src/components
git commit -m "Add components submodule"
git push
This commits both the .gitmodules configuration file and the submodule reference. The commit records the specific SHA of the submodule, not the submodule’s content. Other developers will need to initialize the submodule after cloning your repository.
Adding Submodule with Specific Branch
Track a specific branch of the submodule instead of a detached HEAD:
git submodule add -b develop https://github.com/user/library.git lib/library
The -b flag specifies which branch to track. This is useful when you want to follow a development branch or a specific release branch. The submodule will checkout this branch by default when initialized.
Cloning Repository with Submodules
Clone a repository and automatically initialize its submodules:
git clone --recursive https://github.com/user/main-project.git
The --recursive flag tells Git to automatically initialize and update all submodules in the repository. This saves you from running separate submodule commands after cloning.
Initializing Submodules After Clone
Initialize submodules in an already cloned repository:
git clone https://github.com/user/main-project.git
cd main-project
git submodule init
git submodule update
The git submodule init command registers the submodule paths from .gitmodules into your local .git/config. The git submodule update command clones the submodules and checks out the commit recorded in the parent repository.
Updating Submodules
Update all submodules to their latest commits:
git submodule update --remote
This fetches the latest changes from the remote repository for each submodule and updates them to the latest commit on their tracked branch. Without --remote, the update command checks out the commit recorded in the parent repository.
Updating Specific Submodule
Update a single submodule to the latest commit:
git submodule update --remote path/to/submodule
This updates only the specified submodule, leaving others unchanged. After updating, you need to commit the new submodule reference in the parent repository.
Working Inside a Submodule
Make changes within a submodule directory:
cd path/to/submodule
git checkout main
git pull origin main
# Make changes to files
git add .
git commit -m "Update submodule code"
git push origin main
# Return to parent repository
cd ../..
git add path/to/submodule
git commit -m "Update submodule reference"
git push
Submodules are independent Git repositories, so you use regular Git commands inside them. After making changes in the submodule, remember to commit the updated reference in the parent repository so others can get the new version.
Checking Submodule Status
Check the status of all submodules:
git submodule status
This shows the current commit SHA for each submodule, prefixed with - if the submodule is not initialized, + if it differs from what’s recorded in the parent repository, or U if there are merge conflicts.
Viewing Submodule Configuration
View the .gitmodules file to see submodule configuration:
cat .gitmodules
The file contains entries like this:
[submodule "lib/shared"]
path = lib/shared
url = https://github.com/user/shared-lib.git
branch = main
Each submodule has its path, URL, and optionally a tracked branch defined. This file is committed to the repository and shared with all collaborators.
Removing a Submodule
Remove a submodule from your repository:
git submodule deinit path/to/submodule
git rm path/to/submodule
git commit -m "Remove submodule"
The git submodule deinit command removes the submodule’s entry from .git/config and cleans up the working directory. The git rm command removes the submodule from the repository and .gitmodules. Finally, commit the changes to complete the removal.
Executing Commands in All Submodules
Run a Git command in every submodule:
git submodule foreach 'git pull origin main'
git submodule foreach 'git checkout develop'
git submodule foreach 'git status'
The foreach command executes the specified command in each submodule directory. This is useful for batch operations like pulling updates, checking out branches, or viewing status across all submodules.
Pulling Changes from Remote
Pull updates from both the parent repository and all submodules:
git pull
git submodule update --init --recursive
This ensures you get the latest changes from the parent repository and updates all submodules to match the commits referenced in the parent. The --init flag initializes any new submodules that were added since your last pull.
Alternative One-Command Pull
Use a single command to pull everything:
git pull --recurse-submodules
This pulls changes in the parent repository and automatically updates submodules to their referenced commits. You can make this the default behavior with:
git config --global submodule.recurse true
Pushing Submodule Changes
Push changes from a submodule and update the parent repository:
# Work in submodule
cd path/to/submodule
git add .
git commit -m "Update feature"
git push origin main
# Update parent to reference new commit
cd ../..
git add path/to/submodule
git commit -m "Update submodule to latest version"
git push
Always push submodule changes before pushing the parent repository to ensure other developers can access the submodule commits you’re referencing.
Using Submodule with Private Repositories
Add a submodule from a private repository using SSH:
git submodule add [email protected]:user/private-repo.git lib/private
Make sure team members have SSH keys configured and access to the private repository. You can also use HTTPS with credentials:
git submodule add https://username:[email protected]/user/private-repo.git lib/private
Nested Submodules
Handle submodules that contain their own submodules:
git clone --recursive https://github.com/user/main-project.git
The --recursive flag initializes submodules at all nesting levels. To update nested submodules:
git submodule update --init --recursive
This recursively initializes and updates all submodules and their nested submodules.
Detached HEAD Warning
When working in a submodule, you’ll often see a “detached HEAD” warning:
cd path/to/submodule
git status
# HEAD detached at abc1234
This happens because submodules point to specific commits, not branches. To work on a branch:
cd path/to/submodule
git checkout main
git pull origin main
# Make your changes
git add .
git commit -m "Update code"
git push
cd ../..
git add path/to/submodule
git commit -m "Point to updated submodule"
Always checkout a branch before making changes in a submodule to avoid losing work.
Troubleshooting Submodule Issues
Fix common submodule problems:
# Submodule directory is empty
git submodule update --init --recursive
# Submodule is out of sync
git submodule sync
git submodule update --init --recursive
# Reset submodule to parent's referenced commit
git submodule update --force
# Clean submodule completely and re-clone
git submodule deinit -f path/to/submodule
rm -rf .git/modules/path/to/submodule
git rm -f path/to/submodule
git submodule add https://github.com/user/repo.git path/to/submodule
The git submodule sync command updates submodule URLs if they’ve changed in .gitmodules.
Best Practice Note
At CoreUI, we use Git submodules to share common components and utilities across our admin templates and documentation sites. This approach ensures that updates to shared code are versioned and controlled, preventing unexpected breaks in dependent projects. For complex scenarios where you need more flexibility, consider reading about working with submodules in Git to learn advanced techniques like nested submodules and subtree merging. Remember that submodules work best for code that changes infrequently - for actively developed shared libraries, you might prefer npm packages or monorepo tools. Always document your submodule workflow in your project’s README to help team members understand how to properly initialize and update them.



