Automating README Generation Across My GitHub Projects

Photo by Brett Jordan on Unsplash

The Challenge

Over the past few years, I’ve accumulated a few repositories. Some are experiments, others are demo apps, and many support my YouTube tutorials. As an occasional creator, I wanted all these public facing repositories to have the some additional information in the README.md:

  • Support and donation links
  • Contact information and social handles
  • A consistent footer

For the longest time, I handled this inefficiently: Copy → Paste → Tweak → Repeat.

Every new repo meant manually copying badges and links. Worse, if I decided to change a social handle (which happened more times than I care to admit) or add a new platform, I had to hunt down every old repository and update it one by one. It was a maintenance nightmare. We automate builds, tests, and deployments; so why not documentation?


The Architecture

I needed a system that allowed me to “Write once, update everywhere.”

However, I had a specific constraint: I didn’t want a “push” model where updating a central file immediately triggers a commit on all repositories simultaneously. That approach is difficult to maintain and could break multiple repos if the script has a bug.

Instead, I opted for a “pull” model:

  1. Central Content: One repo to hold shared markdown snippets.
  2. Local Template: A specific template file inside each project.
  3. On-Demand Automation: A GitHub Action that runs independently for each repo.

This setup ensures that I can update the central content once, and then trigger updates on specific repositories individually when I’m ready.


Step 1: Create a Central Content Repository

First, I separated the content from the projects. I created a public repository dedicated solely to shared markdown sections.

Central Repo: github.com/anupdsouza/central-readme

This repo contains modular files like support.md and contact.md. Whenever I need to update a BuyMeACoffee link or change a Twitter handle, I only edit files in this repository. This also allows me to add more sections if I ever need to in the future.


Step 2: Create a Template README

In my actual project repositories, I no longer edit README.md directly. Instead, I created a template file named README_template.md.

This file contains the project-specific documentation plus placeholders for the shared content. See the template README below. I’ve also created a repo containing all these files that you can check out here. This template serves as the starting point for new repositories.

# My Project Name

This is the specific description for this project.

{{support}}

## Features
- Feature A
- Feature B

{{contact}}

Flexibility is key here. Because the script looks for specific placeholders, the injection is entirely optional. If a specific project doesn’t need a support section, I simply omit the {{support}} tag from the template. I can pick and choose exactly which shared blocks appear and where they sit in the layout.


Step 3: The Generator Script

To glue this together, I wrote a small Python script. It reads the local template, fetches the content from the central repository, and writes the final README.md.

import os

TEMPLATE_README = "README_template.md"
# Paths to the cloned central content
SUPPORT_FILE = "central-readme/support.md"
CONTACT_FILE = "central-readme/contact.md"

# 1. Read the template
with open(TEMPLATE_README) as f:
    readme_content = f.read()

# 2. Inject Support Section
if "{{support}}" in readme_content:
    with open(SUPPORT_FILE) as f:
        readme_content = readme_content.replace("{{support}}", f.read())

# 3. Inject Contact Section & Dynamic Links
if "{{contact}}" in readme_content:
    with open(CONTACT_FILE) as f:
        contact_content = f.read()

    # Dynamically insert the current repo URL
    repo_slug = os.getenv("GITHUB_REPOSITORY", "")
    repo_url = f"https://github.com/{repo_slug}"
    contact_content = contact_content.replace("{{repo_url}}", repo_url)

    readme_content = readme_content.replace("{{contact}}", contact_content)

# 4. Write the final file
with open("README.md", "w") as f:
    f.write(readme_content)

Extensibility: This approach makes it incredibly easy to add future sections. If I decide later that I want a standardized “Contributing” or “License” section, I just add the file to the central repo, add a few lines to this script to check for a {{contributing}} tag, and I’m good to go.


Step 4: Automate with GitHub Actions

Finally, I wired everything together with a GitHub Actions workflow.

I encourage you to view the actual workflow file in the template repository to see the implementation:

View Workflow: update-readme.yml

What the workflow does:

  1. Triggers: It runs whenever code is pushed to the main branch, or when triggered manually via workflow_dispatch.
  2. Checkout: It checks out the current repository.
  3. Clone: It clones the central-readme repository to access the shared snippets.
  4. Execute: It sets up Python and runs the generator script shown in Step 3.
  5. Commit: If (and only if) the README.md has changed, the bot commits and pushes the update.

The manual trigger (workflow_dispatch) is essential here. It allows me to go to the Actions tab on GitHub and trigger a README update for a specific repo without needing to push any code.


Final Takeaway

This system isn’t complex architecture; it’s just plumbing: Shared Content + Template + Automation. However, the impact on my workflow has been significant:

New Repos: I copy the template and workflow, push, and I’m done.

Updates: I edit the central repo once.

Maintenance: I can trigger updates individually per repo, keeping the process controlled.

I still have a backlog of older repositories that need to be migrated to this system. However, having a standardized starting point which fits my needs perfectly for all new projects is a massive step forward.


Consider subscribing to my YouTube channel & follow me on X(Twitter). Leave a comment if you have any questions.

Share this article if you found it useful !