Skip to content

Creating GitHub Repositories: A Complete Guide

Lesson 2 of 10Beginner9 min readGitHub Engineering · GitHub FundamentalsVerified: gh 2.98.0 on Ubuntu 24.04, August 2026

Creating a repository takes about fifteen seconds, which is why so many repositories are created badly. The form has half a dozen fields, all of them defaulted, and every default is a decision someone will live with for years.

This lesson covers the three ways to create one — web interface, gh, and pushing an existing local project — and, more usefully, what each field actually does.

A GitHub repository is a bare Git repository on GitHub’s servers, plus a database record carrying everything Git does not model: an owner, a description, visibility, collaborator permissions, feature toggles, and the Issues, pull requests and settings attached to it.

That is why an empty repository still has a URL, a settings page and an Issues tab. There are no commits yet — but the record exists, and everything GitHub adds lives on the record rather than in the Git data.

Consequences worth carrying forward:

  • Deleting a local clone does nothing to the repository. It is a separate copy.
  • A repository can exist with no commits at all, and it is still a fully functional GitHub object.
  • git push transfers Git objects, and nothing else. Issues, settings and pull requests never travel over the Git protocol. Migrating them requires the API.

Every repository belongs to a personal account or an organisation. This is the field people regret most, because it determines the permission model.

A repository owned by you personally has one owner — you. Collaborators can be added individually. There is no concept of teams, and if you disappear, so does administrative access.

A repository owned by an organisation supports teams, role-based permissions, organisation-wide policy and rulesets, and multiple owners. It is also subject to organisation policy, which may restrict what you can do with it.

You can transfer a repository between owners later, and GitHub redirects the old URL. But transfer is a genuine migration — permissions do not follow, and anything referencing the old path by API may need updating. If you know the project belongs to a team, create it under the organisation.

The name becomes part of the clone URL, the Pages URL, package identifiers, and every link anyone makes to it.

Renaming works and old URLs redirect. What does not automatically follow: other people’s configured remotes, hardcoded CI references, package registry names, and documentation elsewhere on the internet.

Practical guidance: lowercase with hyphens, descriptive rather than clever, and no organisation or year encoded into it.

One sentence, shown in search results and on the repository page. It is the only context a stranger gets before deciding whether to keep reading, and it costs ten seconds.

Public — anyone can read it. Private — only people you grant access to.

This deserves more than a sentence, and gets a whole lesson. The critical point for now: choose before you push, not after. Making a repository public later exposes the entire history, not just the current state — and history is where secrets hide.

Adds a first commit containing README.md. Convenient, with one consequence: the repository is no longer empty, so pushing an existing local project needs a merge or a force-push rather than a clean first push.

Rule of thumb: initialise when creating a new project on GitHub first. Do not initialise when you already have local commits to push.

GitHub offers templates per language. Choosing one is worth doing at creation, because the whole point of an ignore file is to be in place before the files it ignores appear. Adding it after committing node_modules does not remove node_modules from history.

You can list and inspect the available templates from the terminal:

Terminal window
gh repo gitignore list
gh repo gitignore view Node

What it doesLists the gitignore templates GitHub offers, then prints one.

Why we run itLets you check what a template actually contains before adopting it, rather than trusting the name.

Expected resultA list of template names, then the contents of the named template.

For public repositories this matters more than most people realise. Code published without a licence is not open source. Default copyright applies, and strictly speaking nobody has permission to use, modify or redistribute it.

If you want people to use your code, pick a licence at creation. If you are unsure, MIT and Apache-2.0 are the common permissive choices, and Apache-2.0 additionally includes an explicit patent grant. For anything with commercial or legal weight, this is a question for someone qualified rather than a default.

Terminal window
gh repo license list
gh repo license view mit

The branch GitHub shows first, and the base branch pull requests target by default. main is GitHub’s default for new repositories.

Change it early or not at all. Renaming a default branch after people have cloned leaves every existing clone tracking a branch that no longer exists.

Best when you want to see the options, or when you are creating a repository for someone else and want the README, licence and ignore file generated for you.

  1. Open the new-repository form from the + menu in the top-right of any GitHub page.
  2. Choose the owner — your account or an organisation you belong to.
  3. Enter a name and a one-line description.
  4. Choose visibility.
  5. Decide whether to initialise with a README, .gitignore and licence.
  6. Create the repository.

GitHub then shows the clone URL and a set of quick-start commands. Those commands are correct, but worth understanding rather than pasting — the next section explains what they do.

Faster, scriptable, and it can create and clone in one step. This is the method to learn.

Terminal window
gh repo create my-project --public --add-readme --license mit --clone

What it doesCreates a public repository under your account, initialises it with a README and an MIT licence, and clones it into the current directory.

Why we run itOne command replaces the form plus a separate clone, and being a command it can go in a script or a project template.

Expected resultA confirmation line with the new repository URL, followed by clone output.

Useful variations:

Terminal window
gh repo create my-project --private --clone
gh repo create my-org/team-project --internal --description "Shared tooling"
gh repo create my-project --private --source=. --remote=origin --push

The third form is the important one: it takes the repository you already have locally, creates the GitHub repository, adds it as origin, and pushes. That is the whole “existing project” flow in a single command.

Run gh repo create --help to see the current flag set — the CLI gains flags regularly, and the help output is authoritative in a way that any tutorial, including this one, is not.

You have a project with commits and no remote. There are two paths.

The manual path:

Terminal window
git remote add origin git@github.com:username/my-project.git
git branch -M main
git push -u origin main

What it doesPoints your local repository at a new empty GitHub repository and pushes the default branch, setting it to track the remote.

Why we run it`-u` records the upstream so later `git push` and `git pull` need no arguments. Without it, every push needs the remote and branch spelled out.

Expected resultObject counting and transfer output, ending with a line confirming the branch is set up to track origin/main.

The CLI path, which does all of it including creating the repository:

Terminal window
gh repo create my-project --private --source=. --remote=origin --push

The same repository is reachable by two URL forms:

https://github.com/username/my-project.git
git@github.com:username/my-project.git

They differ only in how Git authenticates — covered in the previous lesson. You can switch at any time without touching your history:

Terminal window
git remote set-url origin git@github.com:username/my-project.git
git remote -v

Output:

origin git@github.com:username/my-project.git (fetch)
origin git@github.com:username/my-project.git (push)

gh sets this for you according to the Git protocol you selected during gh auth login, which is one fewer decision to make per repository.

Any repository can be marked as a template. Creating from a template copies the files into a brand new repository with a fresh history — a single initial commit, not the template’s commits.

That is the difference from a fork, which preserves history and records a relationship to the original. Templates are for scaffolding; forks are for contribution.

Terminal window
gh repo create my-service --template my-org/service-template --private --clone

Templates are worth reaching for once you have created the same project skeleton three times.

Importing an existing project from elsewhere

Section titled “Importing an existing project from elsewhere”

Moving a project from another host — GitLab, Bitbucket, a self-hosted server — is a Git operation plus a decision about what else you want to bring.

The Git half is a mirror clone, which copies every ref rather than just the default branch:

Terminal window
git clone --mirror https://old-host.example.com/team/project.git
cd project.git
git push --mirror git@github.com:username/project.git

What it doesClones every branch, tag and ref from the old host, then pushes all of them to the new GitHub repository.

Why we run itA normal clone brings the default branch and leaves other branches as remote-tracking refs, so a normal push would publish only part of the history. `--mirror` copies the complete ref namespace.

Expected resultTwo transfers: a full clone, then a push listing every ref created on the new remote.

What a mirror does not bring is everything on the other platform’s side of the boundary: Issues, merge requests, review history, CI configuration specific to that host, and wiki content stored separately. Those need either the source platform’s export tooling or scripted migration through both APIs — the GitHub API cluster covers the receiving half.

It is worth confirming the repository is what you think it is, particularly in a script where nobody is watching:

Terminal window
gh repo view username/my-project --json name,visibility,defaultBranchRef,licenseInfo,isTemplate

Output:

{
"defaultBranchRef": { "name": "main" },
"isTemplate": false,
"licenseInfo": { "key": "mit" },
"name": "my-project",
"visibility": "PUBLIC"
}

Structured output like this is the foundation of everything in the CLI cluster — it is the difference between automation that reads data and automation that parses text and breaks when a column width changes.

Repository settings can also be changed after the fact from the terminal, which is often quicker than finding the right settings page:

Terminal window
gh repo edit username/my-project --description "A better description" --enable-wiki=false

Creation is not the end of configuration. Shortly after, it is worth deciding:

  • Which features are on. Issues, Discussions, Wikis and Projects can each be disabled. A repository with an unused, unmonitored Issues tab is worse than one with none.
  • Merge strategies. Which of merge, squash and rebase are permitted, and whether branches delete automatically after merge. These shape your history — Rebase vs Merge covers the consequences.
  • Branch protection or a ruleset, if anyone other than you will push. The Pull Requests cluster covers this properly.

A repository that will outlive the week benefits from a small set of files, and adding them at creation costs almost nothing.

FileWhy
README.mdWhat this is, how to install, simplest use
LICENSEWithout it, nobody may legally use the code
.gitignoreBefore the artefacts it excludes appear
CONTRIBUTING.mdIf anyone else will contribute
.github/CODEOWNERSIf ownership is not obvious

The README is worth more thought than it usually gets: it is the only thing most visitors read, and the first screen should answer what the project is, who it is for, and what the simplest possible use looks like. Everything else can be a link.

Initialising both ends and force-pushing to fix it. Silently discards the generated README and licence. Create the remote empty when you have local commits.

Publishing without a licence and expecting contributions. No licence means no permission.

Adding .gitignore after the first commit. The ignore file does not retroactively remove anything already tracked.

Choosing private “for now” and going public later without auditing history. Everything ever committed becomes visible, including the credential in commit 3 that was removed in commit 4.

Naming for the current context. new-api-v2-final ages badly.

Creating under a personal account what belongs to a team. Transfer is possible but is a real migration.

Use a disposable repository — you will delete it at the end.

  1. Create a repository with gh repo create, public, with a README and an MIT licence, and clone it.
  2. Confirm the licence and README arrived, and check the default branch name.
  3. In a separate directory, git init a new project, make two commits, and publish it with gh repo create --source=. --push.
  4. Switch that second repository’s remote from HTTPS to SSH, or the reverse, and verify with git remote -v.
  5. Delete both repositories with gh repo delete, which will ask you to confirm.

The point of step 3 is that it is the flow you will use most in real work: the project exists first, and GitHub comes second.

  • A GitHub repository is a bare Git repository plus a record holding everything Git does not model.
  • Owner and visibility are the two fields with lasting consequences; name and default branch are cheap to change early and expensive later.
  • Initialising with a README conflicts with pushing existing local commits — pick one.
  • gh repo create --source=. --push publishes an existing project in one step.
  • Templates copy files with fresh history; forks preserve history and a relationship.
  • Code with no licence is not open source.
Free Git Engineer Cheat Sheet BundleSix printable references plus downloadable toolkit files — commands, recovery, aliases, a CODEOWNERS starter. No email required.