Code That Lives Forever: How DOIs Give Your Projects Eternal Life
Have you ever seen something like https://orcid.org/0000-0002-1825-0097
in a GitHub profile and wondered what it was? Or noticed those mysterious DOI badges on some repositories?
Today, I'll show you how to transform your GitHub repositories from "just another repo" into citable, trackable publications that can boost your professional profile.
What Are ORCID and DOI?
ORCID (Open Researcher and Contributor ID) is like a "social security number" for anyone who contributes to research and knowledge. Despite the name, you don't need to be a traditional researcher to get one.
DOI (Digital Object Identifier) is a permanent link to digital content. Think of it as a persistent URL that never breaks, even if your repository moves. More importantly, it ensures your work is preserved across multiple servers worldwide—so even if GitHub disappears tomorrow, your code remains accessible forever.
Why Should Developers Care?
Instead of just saying "I build cool stuff," you can now say "I have 8 citable publications with measurable impact." Here's why this matters:
- Professional credibility: Your code becomes as credible as academic publications
- Impact tracking: See who cites your work and how it's being used
- Long-term preservation: Your work is archived on multiple servers worldwide
- Platform independence: Your code survives even if hosting platforms disappear
- Career opportunities: Stand out when applying to research-focused companies
- Networking: Connect with researchers and industry experts
- Future-proofing: If you ever want to transition into research or deep tech
The Preservation Advantage
Here's something most developers don't think about: digital preservation. When you get a DOI through Zenodo, your code isn't just stored in one place—it's replicated across multiple servers in different countries, backed by institutions like CERN.
This means:- Your work survives platform shutdowns (remember SourceForge's decline?)
- No vendor lock-in—your DOI works regardless of where you host
- Academic-grade preservation standards (think centuries, not years)
- Automatic format migration when technologies become obsolete
The Magic Triangle: Git Hosting → Zenodo → ORCID
Here's how the integration works: Every time you create a release, Zenodo automatically:- Archives your code permanently
- Generates a unique DOI
- Makes it citable in academic papers
- Updates your ORCID profile
Platform Options
While this guide focuses on GitHub (most popular), Zenodo also integrates with:
- Codeberg - Privacy-focused, European-based alternative
- GitLab - Self-hosted or GitLab.com
- Direct upload - For any git repository
Why consider Codeberg?
- Open source and community-driven
- No corporate surveillance
- European data protection (GDPR)
- Same Zenodo integration as GitHub
Setting It Up (15 Minutes)
Step 1: Get Your ORCID ID
- Go to orcid.org and register (it's free!)
- You'll get an ID like
0000-0002-1825-0097
- Add it to your GitHub profile
Step 2: Prepare Your Repository
Create a CITATION.cff
file in your repo root:
cff-version: 1.2.0
title: "My Awesome Library"
message: "If you use this software, please cite it"
authors:
- family-names: "Your Last Name"
given-names: "Your First Name"
orcid: "https://orcid.org/0000-0000-0000-0000"
repository-code: "https://github.com/yourusername/yourrepo"
license: MIT
version: "1.0.0"
date-released: "2025-06-29"
Step 3: Connect to Zenodo
- Go to zenodo.org
- Sign in with your preferred platform:
- GitHub (most common)
- Codeberg (privacy-focused alternative)
- GitLab
- Go to your profile → Platform settings (e.g., "GitHub" or "GitLab")
- Find your repository and flip the switch (important: do this before releasing!)
If you're using Codeberg, the process is identical—just select "GitLab" in Zenodo (Codeberg uses GitLab's API) and use your Codeberg credentials.
Step 4: Create Your First Release
git tag v1.0.0
git push origin v1.0.0
Or use GitHub's web interface: Releases → Create new release
Step 5: Add the DOI Badge
After Zenodo processes your release (takes a few minutes), add this to your README:
[](https://zenodo.org/badge/latestdoi/YOUR_REPO_ID)
Replace YOUR_REPO_ID
with your GitHub repository ID available at https://api.github.com/repos/username/repo-name
Automation with GitHub Actions
Here is how to harness GitHub Actions to automate two key tasks in your project’s lifecycle: updating your DOI metadata on every release (so your CITATION.cff always reflects the right version and date) and keeping a live, up-to-date mirror of your repository on Codeberg.
Want to fully automate your DOI updates?
Create .github/workflows/update-doi.yml
:
name: Update DOI and Citation
on:
release:
types: [published]
permissions:
contents: write
jobs:
update-citation:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Wait for Zenodo processing
run: sleep 300
- name: Get Zenodo DOI
id: zenodo-doi
run: |
# Replace YOUR_ZENODO_CONCEPT_ID with your actual Zenodo concept ID
# Find this in your Zenodo record's "Cite all versions" section
# Example: if Concept DOI is 10.5281/zenodo.1234567, use 1234567
CONCEPT_ID="1234567"
# Get the latest version DOI from Zenodo API
RESPONSE=$(curl -s "https://zenodo.org/api/records?q=conceptrecid:$CONCEPT_ID&sort=mostrecent")
if [ -z "$RESPONSE" ] || [ "$RESPONSE" = "null" ]; then
echo "Empty response from Zenodo API"
exit 1
fi
# Debug: show the response structure
echo "API Response structure:"
echo $RESPONSE | jq '.hits.hits[0] | {doi: .doi, metadata: .metadata.doi, conceptdoi: .conceptdoi}' || echo "Failed to parse response"
# Extract DOI from the correct path in the response
DOI=$(echo $RESPONSE | jq -r '.hits.hits[0].doi // empty')
# If the main DOI field is empty, try the metadata DOI
if [ -z "$DOI" ] || [ "$DOI" = "null" ]; then
DOI=$(echo $RESPONSE | jq -r '.hits.hits[0].metadata.doi // empty')
fi
# If still empty, construct from the conceptdoi and record ID
if [ -z "$DOI" ] || [ "$DOI" = "null" ]; then
RECORD_ID=$(echo $RESPONSE | jq -r '.hits.hits[0].id // empty')
if [ -n "$RECORD_ID" ]; then
DOI="10.5281/zenodo.$RECORD_ID"
fi
fi
if [ -z "$DOI" ] || [ "$DOI" = "null" ]; then
echo "Failed to get DOI from Zenodo API response"
echo "Response: $RESPONSE"
exit 1
fi
echo "doi=$DOI" >> $GITHUB_OUTPUT
echo "Found DOI: $DOI"
- name: Update CITATION.cff
run: |
# Update version
sed -i "s/^version:.*/version: ${{ github.event.release.tag_name }}/" CITATION.cff
# Update date
sed -i "s/^date-released:.*/date-released: $(date +%Y-%m-%d)/" CITATION.cff
# Update DOI
sed -i "s|^doi:.*|doi: ${{ steps.zenodo-doi.outputs.doi }}|" CITATION.cff
# If there's a preferred-citation section, update it too
if grep -q "preferred-citation:" CITATION.cff; then
sed -i "/preferred-citation:/,/^[^ ]/ { s/^ doi:.*/ doi: ${{ steps.zenodo-doi.outputs.doi }}/; s/^ version:.*/ version: ${{ github.event.release.tag_name }}/; s/^ date-released:.*/ date-released: $(date +%Y-%m-%d)/; }" CITATION.cff
fi
- name: Update README.md DOI badges
run: |
DOI="${{ steps.zenodo-doi.outputs.doi }}"
# Update Zenodo DOI badge (common formats)
sed -i "s|https://zenodo.org/badge/DOI/[^)]*|https://zenodo.org/badge/DOI/$DOI|g" README.md
sed -i "s|https://doi.org/[0-9.]*/zenodo\.[0-9]*|https://doi.org/$DOI|g" README.md
# Update any other DOI references in README
sed -i "s|10\.5281/zenodo\.[0-9]*|${DOI#https://doi.org/}|g" README.md
- name: Update package files (if they exist)
run: |
# Update package.json if it exists
if [ -f "package.json" ]; then
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"${{ github.event.release.tag_name }}\"/" package.json
fi
# Update pyproject.toml if it exists
if [ -f "pyproject.toml" ]; then
sed -i "s/version = \"[^\"]*\"/version = \"${{ github.event.release.tag_name }}\"/" pyproject.toml
fi
# Update setup.py if it exists
if [ -f "setup.py" ]; then
sed -i "s/version=['\"][^'\"]*['\"]/version='${{ github.event.release.tag_name }}'/" setup.py
fi
- name: Verify changes
run: |
echo "=== CITATION.cff changes ==="
git diff CITATION.cff || echo "No changes to CITATION.cff"
echo "=== README.md changes ==="
git diff README.md || echo "No changes to README.md"
echo "=== Other changes ==="
git diff --name-only | grep -v "CITATION.cff\|README.md" || echo "No other changes"
- name: Commit changes
run: |
git config --local user.name "github-actions[bot]"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Add all changed files
git add CITATION.cff README.md package.json pyproject.toml setup.py 2>/dev/null || true
# Only commit if there are changes
if ! git diff --cached --quiet; then
git commit -m "Update DOI and version info for ${{ github.event.release.tag_name }}
- Updated CITATION.cff with new version, date, and DOI
- Updated README.md DOI badges
- Updated package version files
- DOI: ${{ steps.zenodo-doi.outputs.doi }}"
git push
else
echo "No changes to commit"
fi
Want to mirror your repo for an extra backup on Codeberg?
Sign up for a Codeberg account and generate your CODEBERG_TOKEN
.
Create a Codeberg repository using the same name as your GitHub repo.
Add a new workflow file at .github/workflows/mirror.yml
:
name: Mirror to Codeberg
on:
workflow_dispatch:
schedule:
- cron: '30 0 * * 0' # every Sunday at 00:30
push:
branches:
- main
permissions:
contents: read # you’re only reading from GH
jobs:
mirror:
runs-on: ubuntu-latest
# pull your Codeberg API token from secrets
env:
CODEBERG_TOKEN: ${{ secrets.CODEBERG_TOKEN }}
steps:
- name: Checkout full history
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git author
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
- name: Add Codeberg remote & push
run: |
REMOTE="https://${CODEBERG_TOKEN}@codeberg.org/${{ github.repository }}.git"
git remote add codeberg "$REMOTE" || true
git push codeberg --all --force
git push codeberg --tags --force
Real-World Impact
Here are some examples of how this can change your career trajectory:- Data Science Libraries: If you create ML tools, researchers will cite your work in papers. Pandas has 10,000+ citations!
- Developer Tools: Build something useful for the scientific community, and watch your reputation grow.
- Algorithm Implementations: Turn your clever algorithms into cited publications.
Who's Already Doing This
Many successful developers use this approach:- Hadley Wickham (ggplot2, dplyr) - thousands of citations led to leadership roles
- Wes McKinney (pandas) - leveraged citations to found new projects and companies
- John Hunter (matplotlib) - became a legend in the Python community
Getting Started Today
The best time to start was when you created your first repository. The second-best time is now.
- Pick your best repository - something you're proud of
- Follow the 5 steps above - takes 15 minutes
- Create a release - watch the magic happen
- Share it - add your ORCID to your email signature, LinkedIn, etc.
The Bottom Line
In a world where everyone codes, this is how you make your contributions measurable, discoverable, and permanent. It's not about ego—it's about building a professional identity that opens doors and ensuring your work survives the test of time.
Think about it: how many great projects have been lost to dead links, disappeared platforms, or forgotten repositories? With DOIs and proper archiving, your contributions become part of the permanent scientific and technical record.
Your code deserves to be more than just another repository that might vanish someday. Make it count, make it last.
Resources
- ORCID.org - Get your researcher ID
- Zenodo.org - Archive your code and get DOIs
- Citation File Format - Learn more about CITATION.cff
- GitHub-Zenodo Integration Guide - Official documentation
- Codeberg.org - Privacy-focused Git hosting alternative