Writing a Good README
A README is the first file most people see when they land on your repository — GitHub automatically renders it right on the repo’s homepage. A good README explains what your project does, how to install and use it, and how to contribute, in a way that a stranger can follow without asking you a single question. A bad or missing README is one of the most common reasons a genuinely useful project gets ignored.
Overview: What a README Is and Why It Matters
Technically, a README is nothing special to Git itself — it’s just a file, usually named README.md, committed like any other. Git doesn’t know or care that it exists; there is no git readme command. The magic happens on GitHub’s side: when you visit a repository page, GitHub looks in the tree of your default branch for a file named README (case-insensitive) and, if it finds one, renders it as HTML directly below the file listing.
GitHub checks a few locations, in order: the repository root first, then a .github/ folder, then a docs/ folder. It also understands several markup formats — Markdown (.md), plain text (.txt), reStructuredText (.rst), AsciiDoc (.adoc), and a bare README with no extension — but README.md using GitHub Flavored Markdown (GFM) is by far the most common and the one this lesson focuses on.
Where a README Fits in the Object Model
Because a README is just a tracked file, it lives in Git’s object model like everything else: its contents are stored as a blob, referenced by a tree (the snapshot of your project’s root directory), referenced by a commit. When you edit and commit README.md, Git writes a new blob for the new content, a new tree that points at that new blob instead of the old one, and a new commit object pointing at that tree — the branch pointer then moves to the new commit. Nothing about this is README-specific; what makes the file special is purely GitHub’s convention of looking for it by name and rendering it.
Anatomy of a Great README
Most strong READMEs follow a predictable shape, roughly in this order:
- Title and one-line description — what the project is, in plain language.
- Badges (optional) — build status, package version, license, code coverage.
- Description — a short paragraph on the problem it solves and why it exists.
- Table of contents (for longer READMEs) — links to the sections below.
- Installation — exact commands to get it running.
- Usage — a minimal, copy-pasteable example.
- Configuration — environment variables, config files, options.
- Contributing — how to submit changes, link to
CONTRIBUTING.mdif you have one. - License — which license applies, linking to the
LICENSEfile.
Syntax: Markdown Basics for READMEs
A README is written in GitHub Flavored Markdown. You don’t need much of it to write a great one — headings, lists, links, code blocks, and emphasis cover 95% of real-world READMEs.
| Element | Markdown | Purpose |
|---|---|---|
| Heading | ## Section Title |
Creates a section; GitHub auto-generates a link anchor from it |
| Bold / italic | **bold** / *italic* |
Emphasis |
| List | - item or 1. item |
Bulleted or numbered steps |
| Link | (url) |
Hyperlinks, including in-page anchors like #installation |
| Inline code | `code` |
Command names, flags, file names |
| Code fence | triple backticks with a language tag | Multi-line, syntax-highlighted command blocks |
| Blockquote | > note |
Callouts and warnings |
# H1 heading
## H2 heading
**bold text** and *italic text*
- bullet item
- another item
1. first step
2. second step
[Link text](https://example.com)
`inline code`
```bash
echo "fenced code block"
```
> Blockquote for notes
Every ## and ### heading becomes a clickable anchor: GitHub lowercases the heading text, replaces spaces with hyphens, and strips punctuation. A heading ## Getting Started becomes the anchor #getting-started — which is exactly what a table-of-contents link like [Getting Started](#getting-started) points at.
Examples
Example 1: Create and Commit a Minimal README
Every new repository should get a README in its very first commit. This keeps the repo non-empty (which lets you push and branch normally) and gives collaborators context from commit zero.
mkdir todo-cli && cd todo-cli
git init
echo "# todo-cli" > README.md
git add README.md
git commit -m "docs: add initial README"
Output:
Initialized empty Git repository in /home/user/todo-cli/.git/
[main (root-commit) 4f2a9c1] docs: add initial README
1 file changed, 1 insertion(+)
create mode 100644 README.md
git init creates the repository; the echo command writes a single Markdown heading into README.md; git add stages it into the index; git commit writes a blob for the file, a tree for the directory, and a commit object, then moves the main branch pointer to that commit. Push this to GitHub and the heading is all you’ll see rendered on the repo page — a starting point to expand on.
Example 2: A Well-Structured README Template
Here’s a realistic README for a small CLI tool, showing most of the sections described above in one file.
# TaskFlow
A lightweight command-line task manager written in Python.
[](https://github.com/yourname/taskflow/actions)
[](https://opensource.org/licenses/MIT)
[](https://pypi.org/project/taskflow/)
TaskFlow lets you add, complete, and organize tasks from the terminal, with tags and due dates.
## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Configuration](#configuration)
- [Contributing](#contributing)
- [License](#license)
## Installation
```bash
pip install taskflow
```
## Usage
```bash
taskflow add "Write README" --due 2026-08-05 --tag docs
taskflow list --tag docs
```
## Configuration
TaskFlow reads settings from `~/.taskflowrc`. See [docs/configuration.md](docs/configuration.md) for all options.
## Contributing
Pull requests are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting one.
## License
Distributed under the MIT License. See [LICENSE](LICENSE) for details.
Notice the badges at the top are just Markdown images wrapped in links — each one fetches a small SVG from a badge service and links out to the relevant page (Actions, license text, package registry). The table of contents links resolve to auto-generated anchors from the headings below them. And the code fences inside the README (```bash) are themselves Markdown — they get GitHub’s syntax highlighting when rendered, which is why copy-pasteable install and usage commands should always live inside a fenced block, not inline text.
Example 3: Bootstrapping a New GitHub Repo With a README
If you’re starting from scratch on GitHub itself rather than locally, the gh CLI can create the repository and seed it with a README in one step.
gh repo create taskflow --public --add-readme
gh repo view --web
Output:
✓ Created repository yourname/taskflow on GitHub
https://github.com/yourname/taskflow
Opening github.com/yourname/taskflow in your browser.
--add-readme tells GitHub to generate the repository with a starter README.md already committed, so the repo isn’t empty and you can immediately clone it and branch from main. gh repo view --web just opens the new repo page so you can see the rendered result.
How GitHub Renders a README Step by Step
When you load a repository page, GitHub does roughly the following:
- Resolves the default branch (usually
main) and reads the tree at its latest commit. - Searches that tree for a file matching
README(case-insensitive) in the root, then.github/, thendocs/, preferring recognized markup extensions. - Fetches the blob content of the matching file.
- Runs it through its Markdown-to-HTML renderer, sanitizing any raw HTML for safety and applying syntax highlighting to fenced code blocks based on their language tag.
- Generates an anchor id for every heading so in-page and cross-file links (like
#installation) work. - Resolves relative links and images against the current branch and path, so
docs/configuration.mdin the README points at that file as it exists onmain.
This is also why a broken relative link in a README often isn’t obvious until you push: it renders fine in a local Markdown previewer but 404s on GitHub if the target path or branch name doesn’t match reality.
Common Mistakes
Mistake 1: Absolute local file paths for images
Pointing an image at a path that only exists on your own machine breaks it for everyone else.

The fix: commit the image into the repo (e.g. under docs/images/) and reference it with a relative path such as , or upload it through GitHub’s drag-and-drop editor, which gives you a stable user-images.githubusercontent.com URL.
Mistake 2: No license section, and no LICENSE file
Without a LICENSE file, GitHub shows a banner warning that the project has no license, and by default copyright law means nobody else has permission to use, modify, or redistribute your code — even if it’s public. Add a LICENSE file (GitHub’s repo creation flow can generate one for you) and link to it from a License section in the README.
Mistake 3: A wall of unstructured text
A README that’s one long paragraph with no headings is hard to scan, and GitHub can’t build a useful table of contents or anchors from it. Break content into ## sections (Installation, Usage, Configuration, and so on) so readers can jump straight to what they need.
Mistake 4: Stale badges after renaming the default branch
A CI badge URL often hardcodes a branch name, like ?branch=main. If you rename your default branch (e.g. from master to main) without updating the badge URL, it silently shows the wrong status or a broken image. Update badge URLs whenever you rename branches or move CI workflow files.
Best Practices
- Put installation and usage instructions near the top — most readers never scroll past the first screen.
- Show a copy-pasteable command block for the fastest path to running the project.
- Keep the README in sync with the code; treat outdated instructions as a bug.
- Use relative links for anything inside the repo so they work on forks and other branches too.
- Add a Contributing section (or link to
CONTRIBUTING.md) if you want pull requests from others. - Prefer a small number of meaningful badges over a long row of decorative ones.
- Write commit messages for README changes the same way as code, e.g.
docs: clarify installation steps, following Conventional Commits. - For large projects, keep the root README short and link out to a
docs/folder for deep detail.
Practice Exercises
- Create a new local repository, add a
README.mdwith a title, a one-paragraph description, and an Installation section, then commit it with a Conventional Commits-style message. Push it to GitHub and confirm it renders on the repo homepage. - Add a Table of Contents to that README linking to at least three
##sections using anchor links, and verify each link actually jumps to the right section on GitHub. - Intentionally add an image reference with a broken relative path, push it, and observe the broken image on GitHub. Then fix it by committing the image into a
docs/images/folder and updating the path.
Summary
- A README is just a tracked file (usually
README.md) that GitHub automatically finds and renders on your repo’s homepage. - GitHub looks for it in the root, then
.github/, thendocs/, and renders it with GitHub Flavored Markdown. - Good READMEs follow a predictable structure: title, description, badges, table of contents, installation, usage, configuration, contributing, and license.
- Headings auto-generate anchors, which is what makes in-page table-of-contents links work.
- Common failures are broken relative paths, missing license information, unstructured walls of text, and stale badges.
- Commit and update your README the same way you commit code — as part of the same workflow, not an afterthought.
