Overleaf Git Integration
If your experiment code lives in a Git repository and your paper lives on Overleaf, you have probably done the upload dance: rerun an experiment, hunt down the regenerated figure on disk, open Overleaf, delete the old file, upload the new one, and repeat for every figure and table that changed. It is slow, it is easy to leave a stale figure behind, and it leaves no record of which version of your code produced which figure in the paper.
Overleaf Premium's Git integration removes most of this busywork. Every Overleaf project can be cloned as an ordinary Git repository, with no GitHub account or extra repository required. This article describes one way to use it: cloning the Overleaf project inside your experiment repository, in a directory that the experiment repository ignores. Your analysis scripts then write figures and tables straight into the paper, and a single git push puts them on Overleaf.
How It Works
There are several ways to connect a code repository to an Overleaf project. In this one, the two repositories stay completely independent. The Overleaf project is cloned into a subdirectory (here called paper/), and that subdirectory is listed in the experiment repository's .gitignore. The experiment repository never records anything about the paper, and the paper never sees your experimental code. Your scripts simply write files into a folder that happens to be a different Git repository, one whose remote is Overleaf itself.
The final layout looks like this:
my-experiments/ ← experiment repo
├── .gitignore ← contains "paper/"
├── src/
├── scripts/
│ └── make_figures.py ← writes into paper/figures and paper/tables
├── Makefile
└── paper/ ← SEPARATE git repo, ignored by the outer repo
├── .git/ ← remote: https://git.overleaf.com/<project-id>
├── main.tex
├── refs.bib
├── figures/ ← written by code
│ ├── coverage.pdf
│ └── PROVENANCE.txt ← which code version made these
└── tables/ ← written by code
└── results.tex
Changes flow directly in both directions, with nothing in between:
scripts/make_figures.py ──writes──▶ paper/figures, paper/tables paper/ ──git push──▶ Overleaf Overleaf ──git pull──▶ paper/
| Strengths | Weaknesses |
|---|---|
|
|
| 🔗 Overleaf Documentation: Git Integration | |
Setting It Up
Step 1: Generate a Git authentication token
Overleaf doesn't accept your account password over Git. In Overleaf, open Account Settings, find the Git Integration section, and generate a token. Copy it somewhere safe right away, because Overleaf will only show it once.

Step 2: Find your project's clone URL
Open your paper in Overleaf, click Menu in the top left, and choose Git under the Sync section. The dialog shows a git clone command with your project's URL, which looks like https://git.overleaf.com/<project-id>. The project ID is the same string that appears in your browser's address bar when the project is open.

Step 3: Clone the paper inside your experiment repository
From the root of your experiment repository, tell Git to ignore the paper/ directory before you clone into it:
cd my-experiments
echo "paper/" >> .gitignore
git add .gitignore
git commit -m "Ignore nested paper clone"
git clone https://git.overleaf.com/<project-id> paperWhen Git asks for credentials, enter git as the username and your token as the password. To avoid typing the token every time, let Git remember it with your system's credential helper (e.g. osxkeychain on macOS, manager on Windows). On shared machines, avoid the plain-text store helper.
Then check that the two repositories don't see each other:
git status # should NOT list paper/
git -C paper status # shows the Overleaf project, on branch master
Step 4: Point your code at the paper directory
Rather than hard-coding paths in every script, define the paper location once. Using an environment variable with a sensible default means the code still runs for people who haven't cloned the paper (their figures just land in an ignored folder).
# src/paths.py
import os
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
PAPER_DIR = Path(os.environ.get("PAPER_DIR", REPO_ROOT / "paper"))
FIG_DIR = PAPER_DIR / "figures"
TAB_DIR = PAPER_DIR / "tables"
FIG_DIR.mkdir(parents=True, exist_ok=True)
TAB_DIR.mkdir(parents=True, exist_ok=True)For figures, save vector PDFs directly into the paper. Removing the creation timestamp means a figure that didn't actually change produces an identical file, so Git won't report a pointless change:
import matplotlib.pyplot as plt
from src.paths import FIG_DIR
fig, ax = plt.subplots(figsize=(3.25, 2.4)) # match your template's column width
...
fig.savefig(FIG_DIR / "coverage.pdf", bbox_inches="tight",
metadata={"CreationDate": None})For tables, write a LaTeX fragment rather than a full document:
from src.paths import TAB_DIR
tex = df.to_latex(index=False, escape=True, float_format="%.3f")
(TAB_DIR / "results.tex").write_text(tex)and pull both into the paper as usual:
\includegraphics[width=\linewidth]{figures/coverage}
...
\input{tables/results}For numbers quoted in the text, the same idea works. Have your code write LaTeX macros so the prose never goes stale:
(TAB_DIR / "numbers.tex").write_text(
f"\\newcommand{{\\coverageNinetyFive}}{{{cov95:.1f}\\%}}\n"
)% preamble
\input{tables/numbers}
% body
Our intervals achieve \coverageNinetyFive{} empirical coverage.Day-to-Day Workflow
Sending new results to the paper
After rerunning your experiments, commit and push from inside paper/:
python scripts/make_figures.py
cd paper
git pull # pick up any text edits made on Overleaf first
git add figures tables
git commit -m "Regenerate figures"
git pushThat's it. Refresh the Overleaf editor and recompile, and the new figures are there.
Recording provenance
Since the two repositories aren't linked, have your script record which code produced the figures. Overleaf's Git integration does not reliably preserve your local commit messages, so write the information into a file in the paper instead:
SHA=$(git rev-parse --short HEAD)
git diff --quiet || SHA="$SHA-dirty"
echo "Generated from my-experiments@$SHA on $(date -u +%Y-%m-%dT%H:%MZ)" > paper/figures/PROVENANCE.txtThe -dirty suffix flags figures made from uncommitted code. Months later, opening PROVENANCE.txt (or its history in Overleaf) answers "where did these figures come from?"
Automating it with a Makefile
Once the steps above feel familiar, wrap them in a single command:
PAPER := paper
SHA := $(shell git rev-parse --short HEAD)$(shell git diff --quiet || echo -dirty)
.PHONY: figures paper-sync
figures:
python scripts/make_figures.py
paper-sync: figures
git -C $(PAPER) pull --rebase
git -C $(PAPER) diff --quiet -- figures tables || \
echo "Generated from my-experiments@$(SHA) on $$(date -u +%Y-%m-%dT%H:%MZ)" > $(PAPER)/figures/PROVENANCE.txt
git -C $(PAPER) add figures tables
git -C $(PAPER) diff --cached --quiet || \
git -C $(PAPER) commit -m "Regenerate figures from my-experiments@$(SHA)"
git -C $(PAPER) pushNow make paper-sync regenerates everything, pulls in your co-authors' edits, updates the provenance file only when a figure or table actually changed, skips the commit entirely if nothing changed, and pushes straight to Overleaf.
Avoiding conflicts
Conflicts are rare if every file has exactly one "owner":
figures/ and tables/ belong to your code. Never edit them by hand on Overleaf; the next regeneration will silently overwrite your changes. Change the formatting in the code that generates them instead.
.tex and .bib files belong to people. Write them in the Overleaf editor (or locally, then push), and always pull before you push figures.
Why Not a Git Submodule?
Git submodules are the "official" way to put one repository inside another, so it is natural to ask why this workflow avoids them.
| Gitignored Clone | Git Submodule | |
|---|---|---|
| Experiment repo records the paper version | No | Yes (a pointer) |
| Extra commit in experiment repo per paper change | No | Yes |
| Collaborators need Overleaf credentials to clone the code | No | Yes, for a full clone |
| Link between figure and code version | Provenance file | Automatic |
A submodule gives you exact, automatic provenance, but at a price. Every change to the paper, including every typo fix a co-author makes on Overleaf, needs a matching "bump the submodule" commit in your experiment repository. On top of that, anyone cloning your code with submodules would need an Overleaf token. For a paper that is edited daily, this usually isn't worth it. The gitignored clone trades automatic provenance for a one-line provenance file, which is plenty in practice.
Variations
Want a GitHub copy of the paper? Overleaf's GitHub Synchronization keeps the project mirrored in a GitHub repository, which you can clone into the gitignored directory in exactly the same way. You gain issues, pull-request reviews, and an off-site backup. The cost is a manual sync step, since Overleaf only exchanges changes with GitHub when someone clicks a button in the Overleaf menu.
Generating figures on a cluster: Clone the Overleaf project on the cluster as well, set up your token with a credential helper, and run make paper-sync there. Alternatively, point PAPER_DIR at a scratch directory on the cluster, copy the outputs back to your laptop, and push from there, so your token never leaves your own machine.
Common Pitfalls
| Things to Watch Out For |
|---|
|
Note: This is just one of several ways to connect your code and your paper. If you want your paper on GitHub, try GitHub Synchronization instead; if you want airtight provenance, a submodule may be worth the extra commits.