Submodules: When a Laravel Team Actually Needs One

Submodules: When a Laravel Team Actually Needs One

21 October 2026 · By Laravel Git · 2 min read

Someone suggests a submodule the first time two apps need the same code. It sounds like sharing; what it actually does is pin a commit of one repository inside another. Sometimes that is exactly what you want. For PHP code, it usually is not.

Why they have a reputation#

A submodule is a second repository with its own history, and Git only records which commit it sits on. That design creates the daily friction:

  • A plain git clone or git pull does not fetch submodule contents - the directory sits empty until git submodule update --init --recursive.
  • The submodule’s HEAD is detached by default, so committing inside it needs the same care as any detached state.
  • Updating means committing in the submodule, pushing it, then committing the new pin in the parent - two commits, two pushes, and a parent repo that is wrong if either is forgotten.
  • CI, deploy hooks and new teammates each get to discover all of this once.

The Laravel answer for shared code#

If the shared thing is PHP - a domain package, SDK wrappers, shared DTOs - make it a Composer package instead. A private package on Satis or Private Packagist, or even a path repository while it is small, gives you real versioning: composer update, semver constraints, and a lock file that says exactly what runs where. That is strictly better than a pinned commit with no version number, and every Laravel developer already knows the workflow.

When the submodule is right#

The honest cases:

  • Shared non-PHP content - infrastructure definitions, deployment config, a docs site - where Composer cannot carry it anyway.
  • Vendoring a third-party project you patch and need to track against upstream, where forking plus subtree is heavier than the pin.
  • You genuinely want an exact, reviewable pin with no registry involved: the parent repo’s history shows every bump as a normal commit.

If you take one on#

git clone --recurse-submodules git@yourserver:app.git
git config submodule.recurse true

The clone flag populates the submodule from day one; the config makes ordinary pulls update it too. Both remove the class of mistake that gives submodules their name, because the empty-directory surprise is the one people hit first.

The rule of thumb: PHP code becomes a package; everything else is allowed to be a submodule - with the flags set before the first teammate clones it.

Share this post

Our network