Lesson 92 of 12120 min read

Submodules: Including External Repos Inside a Repo, Updating Submodules

Learn how Git submodules embed one repository inside another as a linked, version-pinned reference, and how to clone and update them correctly.

Author: CodersNexus

Submodules: Including External Repos Inside a Repo, Updating Submodules

Sometimes a project needs to include another, entirely separate Git repository as part of its own structure — a shared internal library, a vendored dependency, or a component maintained in its own repository with its own independent history. Git submodules provide a way to embed exactly this: a reference to another repository, pinned to a specific commit, living inside your own repository's folder structure.

Learning Objectives

  • Explain what a Git submodule is and what problem it solves.
  • Add a new submodule to a repository.
  • Correctly clone a repository that contains submodules.
  • Update a submodule to a newer commit of the external repository.

Key Terms to Know Before Using Git Submodules

  • Git submodule: A reference embedded within a repository, pointing to a specific commit of an entirely separate, external Git repository.
  • .gitmodules file: A configuration file, automatically created and tracked, recording each submodule's path and source URL.
  • Submodule detached HEAD: The default state of a submodule's checkout — pointing directly at the pinned commit rather than any branch, since it's meant to reference an exact, fixed version.
  • Recursive clone: Cloning a repository along with the correct commit of every submodule it references, in one combined operation.

How Git Submodules Actually Work

A **Git submodule** lets you embed a reference to an entirely separate, external repository inside your own project — for example, a shared internal component library maintained in its own independent repository, included as a subfolder within a larger application's repository. Critically, a submodule doesn't merge or copy that external repository's history into your own — it stores a reference **pinned to one specific commit** of the external repository, along with its source URL, recorded in a `.gitmodules` file at your repository's root.

Adding a new submodule:

```
git submodule add https://github.com/org/shared-library.git libs/shared-library
```

This clones the external repository into the specified subfolder (`libs/shared-library`), pins it at whatever commit was current at the time (typically its default branch's latest commit), and adds an entry to `.gitmodules` recording this configuration — which itself gets committed to your main repository, just like any other tracked file.

A critical detail for anyone cloning a repository that contains submodules: a **plain `git clone` does not automatically populate submodule content** — it creates the empty subfolder(s) where submodules belong, but leaves them uninitialized. Correctly cloning with submodules requires either an extra explicit step:

```
git clone https://github.com/org/main-project.git
cd main-project
git submodule init
git submodule update
```

or, more conveniently, a single combined **recursive clone**:

```
git clone --recurse-submodules https://github.com/org/main-project.git
```

which handles cloning the main repository and correctly initializing every submodule's pinned commit in one step — a detail worth remembering, since forgetting it is one of the most common submodule-related points of confusion (an incomplete clone with empty submodule folders).

Because a submodule is pinned to one specific commit rather than tracking a branch, checking it out puts it in a **detached HEAD** state (Module 3) by default — this is intentional, reflecting the fact that a submodule represents a fixed, specific version, not an actively moving branch. **Updating a submodule** to a newer commit of the external repository is a deliberate, explicit action:

```
cd libs/shared-library
git fetch
git checkout <newer-commit-or-tag>
cd ../..
git add libs/shared-library
git commit -m "chore: update shared-library submodule to v2.1.0"
```

Notice the final commit is made in the **main repository**, recording the updated pinned commit reference — the submodule update itself needs to be explicitly committed in the parent repository, exactly like any other tracked file change, since the pinned reference is what's actually stored there.

Git Submodules: Visual Walkthrough

Draw a main repository folder 'my-app/' containing regular files (index.js, package.json) alongside a subfolder 'libs/shared-library/' shown with a DASHED border, labeled 'Submodule — pinned to commit a1b2c3d of a SEPARATE repository.' Draw an arrow from this subfolder to an entirely separate repository icon labeled 'shared-library (its own independent history, its own commits).' Show a '.gitmodules' file in the main repo listing: 'path = libs/shared-library, url = https://github.com/org/shared-library.git'. Caption: 'The main repo stores a REFERENCE to one specific commit, not a copy of the submodule's full history.'

Submodule Commands: Quick Reference Table

CommandPurpose
git submodule add <url> <path>Adds a new submodule, cloning it into the specified path and pinning its current commit
git clone --recurse-submodules <url>Clones a repository AND correctly initializes all its submodules in one step
git submodule init && git submodule updateThe two-step equivalent, used after a plain clone left submodules empty
git submodule update --remoteUpdates a submodule to the latest commit on its tracked branch, rather than a manually chosen commit

Submodules: Command Syntax and Examples

# Add a new submodule to a project
git submodule add https://github.com/org/shared-library.git libs/shared-library
git commit -m "feat: add shared-library as a submodule"

# Correctly cloning a project that HAS submodules (combined, recommended approach)
git clone --recurse-submodules https://github.com/org/my-app.git

# Equivalent two-step approach, if you already did a plain clone
git clone https://github.com/org/my-app.git
cd my-app
git submodule init
git submodule update

# Updating a submodule to a newer commit and committing that change in the MAIN repo
cd libs/shared-library
git fetch
git checkout v2.1.0
cd ../..
git add libs/shared-library
git commit -m "chore: update shared-library submodule to v2.1.0"

Breaking Down the Submodules Example

`git submodule add` demonstrates embedding an external repository, immediately followed by committing the resulting `.gitmodules` entry to the main repository, since this configuration is itself tracked. The recursive clone example shows the recommended, single-step way to correctly clone a project along with all its submodule content already populated, contrasted with the equivalent (but easier to forget) two-step `init`/`update` sequence needed after a plain clone. The final block demonstrates the deliberate process of updating a submodule: checking out a newer commit *inside* the submodule's own folder, then returning to the main repository to explicitly commit this updated pinned reference — a two-level commit process that reflects the submodule's fundamentally separate, independent nature.

How Submodules Are Used on Real Engineering Teams

  • Companies with multiple internal applications sharing a common component library or design system sometimes use submodules to include that shared code as a version-pinned dependency within each application's own repository.
  • Open-source projects that vendor (include a specific, fixed version of) an external dependency's source code directly, rather than relying on a package manager, commonly use submodules to manage that inclusion cleanly.
  • Forgetting the --recurse-submodules flag (or the equivalent init/update steps) when cloning a project is one of the most frequently reported points of confusion in projects using submodules, often leading to confusing missing-file errors until the omission is understood.
  • Some teams have moved away from submodules in favor of alternative approaches (like the git subtree covered in the next lesson, or simply using a proper package manager) specifically because submodules' two-level commit workflow and easy-to-forget clone step create real friction for less experienced contributors.

Submodules Interview Questions and Answers

Q1. What is a Git submodule, and what does it actually store in the parent repository?

A submodule embeds a reference to a specific commit of an entirely separate, external repository within your own repository's folder structure. Rather than copying that external repository's history, the parent repository only stores a reference pinned to one exact commit, recorded in a .gitmodules file, along with the submodule's source URL.

Q2. Why doesn't a plain git clone automatically populate a repository's submodule content, and how do you correctly clone a project that uses them?

A plain clone only creates the empty subfolder(s) where submodules belong, without populating their actual content, since submodules are a separate, deliberate step. Correctly cloning requires either git clone --recurse-submodules (a combined, recommended approach) or a plain clone followed by git submodule init and git submodule update.

Q3. Why does checking out a submodule typically result in a detached HEAD state, and how do you update it to a newer version?

A submodule is pinned to one specific commit rather than tracking a branch, so checking it out reflects exactly that fixed version, resulting in detached HEAD, consistent with Module 3's explanation of that state. Updating requires checking out a newer commit inside the submodule's own folder, then returning to the parent repository and explicitly committing the updated pinned reference there.

Submodules Quiz: Test Your Understanding

1. What does a Git submodule actually store in the parent repository?

  1. A full copy of the external repository's entire history
  2. A reference pinned to one specific commit of the external repository
  3. Nothing — submodules are purely conceptual
  4. A compressed archive of the external repository's files

Answer: B. A reference pinned to one specific commit of the external repository

Explanation: A submodule embeds only a pointer to one exact commit of a separate repository, recorded in .gitmodules, rather than copying or merging that repository's full history.

2. What happens when you run a plain git clone on a repository that contains submodules?

  1. The submodules are automatically fully populated
  2. Empty subfolders are created where submodules belong, but their content is NOT automatically populated
  3. The clone fails entirely
  4. Only the submodule's .gitmodules file is downloaded, with no folder created at all

Answer: B. Empty subfolders are created where submodules belong, but their content is NOT automatically populated

Explanation: A plain clone leaves submodule folders empty and uninitialized; populating them requires either git clone --recurse-submodules or a subsequent git submodule init/update.

3. Why does a submodule typically end up in a detached HEAD state after being checked out?

  1. It's a bug in Git
  2. A submodule is pinned to one specific commit rather than tracking a branch, so HEAD points directly at that commit
  3. Submodules never have a HEAD state
  4. It only happens if the submodule has conflicts

Answer: B. A submodule is pinned to one specific commit rather than tracking a branch, so HEAD points directly at that commit

Explanation: Since a submodule represents a fixed, specific version rather than an actively moving branch, checking it out naturally results in a detached HEAD, pointing directly at that pinned commit.

Common Submodule Mistakes Beginners Make

  • Cloning a repository with a plain git clone and forgetting the --recurse-submodules flag (or the init/update steps), resulting in confusing, empty submodule folders.
  • Forgetting that updating a submodule requires an explicit commit in the PARENT repository to record the new pinned commit reference.
  • Being confused by a submodule's detached HEAD state, not realizing this is expected and intentional given how submodules represent a fixed, pinned version.
  • Manually editing files inside a submodule without understanding those changes belong to an entirely separate repository with its own independent history and remote.

Submodules: Exam-Ready Quick Notes

  • Submodule: a reference pinned to one specific commit of a separate, external repository, recorded in .gitmodules.
  • git submodule add <url> <path>: embeds a new submodule.
  • Cloning with submodules: git clone --recurse-submodules <url>, or plain clone + git submodule init + git submodule update.
  • Updating: checkout a newer commit INSIDE the submodule folder, then commit the updated reference in the PARENT repository.

Submodules: Key Takeaways

  • Git submodules embed a reference to one specific, pinned commit of an external repository, not a copy of its full history.
  • Correctly cloning a repository with submodules requires an extra step (--recurse-submodules, or init/update) that's easy to forget.
  • Updating a submodule is a two-level process: checking out a new commit inside the submodule, then explicitly committing that updated reference in the parent repository.

Frequently Asked Questions About Git Submodules

Q1. What is a Git submodule?

It's a way to embed a reference to a specific commit of an entirely separate, external Git repository inside your own repository's folder structure — useful for including a shared library or component maintained in its own independent repository.

Q2. Why doesn't cloning a repository automatically include its submodules' content?

A plain git clone only creates empty placeholder folders where submodules belong, without populating their actual content, since populating submodules is treated as a separate, deliberate step, either through the --recurse-submodules flag or explicit init/update commands.

Q3. How do I correctly clone a repository that has submodules?

Use git clone --recurse-submodules <url>, which clones the main repository and correctly initializes all its submodules in a single combined step, avoiding the need for separate follow-up commands.

Q4. Why is my submodule in a 'detached HEAD' state?

This is expected behavior. A submodule is pinned to one specific commit rather than tracking a branch, so checking it out naturally results in HEAD pointing directly at that fixed commit rather than at any branch.

Q5. How do I update a submodule to a newer version?

Go into the submodule's folder, fetch and check out the newer commit or tag you want, then return to your main (parent) repository and commit that change there — the parent repository needs its own explicit commit to record the updated pinned reference.

Summary

Git submodules let you embed a reference to a specific, pinned commit of an entirely separate external repository inside your own repository's folder structure, recorded in a `.gitmodules` file, without copying or merging that external repository's full history. A plain `git clone` doesn't automatically populate submodule content — it leaves empty subfolders that require either the combined `git clone --recurse-submodules` command or the equivalent `git submodule init` and `git submodule update` steps to correctly populate. Because a submodule is pinned to one specific commit rather than tracking a branch, checking it out results in a detached HEAD state by design. Updating a submodule to a newer version is a deliberate, two-level process: checking out the desired newer commit inside the submodule's own folder, then returning to the parent repository to explicitly commit that updated pinned reference, since that reference is what the parent repository actually tracks.

Frequently Asked Questions

It's a way to embed a reference to a specific commit of an entirely separate, external Git repository inside your own repository's folder structure — useful for including a shared library or component maintained in its own independent repository.

A plain git clone only creates empty placeholder folders where submodules belong, without populating their actual content, since populating submodules is treated as a separate, deliberate step, either through the --recurse-submodules flag or explicit init/update commands.

Use git clone --recurse-submodules <url>, which clones the main repository and correctly initializes all its submodules in a single combined step, avoiding the need for separate follow-up commands.

This is expected behavior. A submodule is pinned to one specific commit rather than tracking a branch, so checking it out naturally results in HEAD pointing directly at that fixed commit rather than at any branch.

Go into the submodule's folder, fetch and check out the newer commit or tag you want, then return to your main (parent) repository and commit that change there — the parent repository needs its own explicit commit to record the updated pinned reference.