Git and GitHub for plugin developers
Save every version of your plugin, undo mistakes and share code safely.
Sooner or later you break a plugin that worked an hour ago. Git is the save system that lets you go back. On this page you will install Git, turn it on in IntelliJ, make your first commits, undo a mistake, and put your plugin on GitHub without leaking a password.
What is version control?
Think about how you play a hard Minecraft world. Before you fight the Ender Dragon you make a backup copy of the world. If the fight goes badly, you restore the backup and try again. You do not lose the base you spent hours on.
Version control is that idea for code. Instead of copying your whole project folder and naming the copy my-plugin-final-FINAL-2, you tell a tool "remember the project exactly as it is right now". The tool stores a snapshot and gives it a note, so you can look back or return to it at any time.
Git is the most popular version control tool, and the one every Minecraft plugin author uses. Each snapshot is called a commit. A project with all its commits is a repository (people say "repo"). Your repo lives in a hidden folder called .git inside your project, so it stays on your own PC.
Git gives you three things that matter for plugins:
- Undo. You can return to any earlier commit, or bring back one file as it used to be.
- History. Each commit says what changed and when, so you can find out which change broke your plugin.
- Sharing. You can copy your repo to a website called GitHub, so it is backed up and other people can read it. Git is the tool on your PC. GitHub is the website that stores copies of Git repos.
Install Git on Windows
Install it with winget
Open PowerShell and run this.
wingetis Windows' built-in installer.PowerShellwinget install --id Git.Git -eFound Git [Git.Git] Version 2.51.0Successfully installedYou can also download the installer from git-scm.com and click through it. The default choices are fine.
Open a new PowerShell window and check
Close PowerShell and open it again, so it notices the new program. Then ask Git for its version:
PowerShellgit --versiongit version 2.51.0.windows.1Tell Git who you are
Every commit records its author. Set your name and email once. The email can be the one you will use on GitHub. The last line makes the first branch of every new repo be called
main.PowerShellgit config --global user.name "Jace" git config --global user.email "you@example.com" git config --global init.defaultBranch mainUse your own name and email, not these examples. Your email becomes part of your commits, and it is visible if you put the repo on a public site. GitHub lets you hide your real address behind a private one.
Turn on Git in IntelliJ
IntelliJ has Git built in. It uses the Git you just installed, and it shows what changed with colors in the file list and next to the line numbers.
Open your plugin project
Open the project folder as usual (see Anatomy of a plugin project).
Create the repository
Choose , pick Git in the list, and press OK. In some IntelliJ versions the same command is called . Behind the scenes IntelliJ runs
git initin your project folder.Look at the Commit window
Press Ctrl+K (or open the Commit tab on the left). Nothing is saved yet. IntelliJ only lists files that Git has not recorded.
What to commit and what to leave out
Your project folder holds two kinds of files. Some you write: your Java code, plugin.yml, build.gradle.kts. Others are made by tools: compiled classes, the finished jar, and the whole test server that Run Paper Server puts in run/. Only commit the files you write. Generated files are huge, change on every build, and can be rebuilt at any time.
- my-plugin/
- .gradle/Gradle's cache. Generated. Do not commit.
- .idea/IntelliJ's personal settings. Do not commit.
- build/Compiled classes and your plugin jar. Generated. Do not commit.
- run/The test server: worlds, logs, other plugins. Do not commit.
- gradle/
- wrapper/
- gradle-wrapper.jarLets anyone build with ./gradlew. DO commit this one.
- wrapper/
- src/Your code and plugin.yml. Commit.
- build.gradle.ktsThe build recipe. Commit.
- settings.gradle.ktsThe project name. Commit.
- gradle.propertiesVersion numbers. Commit.
- .gitignoreThe list of things Git must skip. Commit.
You tell Git what to skip with a file named .gitignore in the project's top folder. In IntelliJ, right-click the project folder in the Project view, choose and type .gitignore. Each line is a name or pattern. Git acts as if matching files do not exist.
1.gradle/2build/3run/4out/5.idea/6*.iml7.kotlin/- A trailing slash means a folder. Git skips the folder and everything in it.
- Your compiled jar lives in
build/libs. You share the jar through a release (see Releasing and sharing your plugin), not through commits. - The test server's worlds and logs. They can be hundreds of megabytes, and they may hold player data.
- The star matches any name, so this skips every IntelliJ module file.
A .gitignore only works for files Git has not recorded yet. If you committed build/ by mistake before adding the line, tell Git to forget it with git rm -r --cached build and commit again. The files stay on your disk.
Your first commit
A commit has two parts: the changes you chose, and a short message that says what and why. IntelliJ shows both in the Commit window:
Tick the files
Files under Changes are ticked by default. Check that
.gitignoreis in the list and thatbuild/,run/and.idea/are not. Leave out anything else you do not want in this snapshot. Files under Unversioned Files are new to Git; tick them to start tracking them.Write a message
Say what the commit does, in the present tense: "Greet players when they join". A message such as "stuff" or "fix" is useless in six months.
Press Commit
The snapshot is saved on your PC. Commit and Push... also copies it to GitHub; you will set that up in a moment.
The same thing from the terminal in the project folder (IntelliJ's Terminal tab opens it for you). This is useful to know, because every tutorial shows commands:
git statusOn branch mainUntracked files: .gitignore build.gradle.kts src/ git add . git commit -m "Greet players when they join"[main (root-commit) 3f2a9c1] Greet players when they join 6 files changed, 74 insertions(+) git log --oneline3f2a9c1 (HEAD -> main) Greet players when they joingit add . moves every changed file into the staging area: the list of changes that will go into the next commit. git commit then saves exactly that list. IntelliJ does both when you press Commit.
Commit small and often: when you finish one thing that works, such as "add the /heal command". A good rhythm is one commit every time the plugin compiles and does something new.
Looking back and undoing mistakes
Here are the four undo tools you will use. They go from gentle to strong.
See what changed
In IntelliJ, right-click a file and choose to see every commit that touched it. Click a commit to see the lines that changed: green was added, red was removed. In the editor, the colored bar next to the line numbers marks lines you changed since the last commit; click it and press the arrow to roll back just that change. In the terminal:
git log --onelineb81d0e4 (HEAD -> main) Add /heal command3f2a9c1 Greet players when they join git diff- player.sendRichMessage("<green>Welcome!");+ player.sendRichMessage("<red>Welcome??");Throw away changes to one file
You edited JoinListener.java, broke it, and want it exactly as it was at the last commit:
git restore src/main/java/com/example/myplugin/JoinListener.javaBring back an old version of one file
Find the commit in git log --oneline, then ask for the file as it was in that commit:
git restore --source=3f2a9c1 src/main/java/com/example/myplugin/JoinListener.javaThe old version appears in your folder as a change. You can compile it, test it, and commit it, so the restore itself is recorded.
Undo a whole commit safely
git revert adds a new commit that does the exact opposite of an older one. History is not rewritten, so it is safe even after you pushed to GitHub. In IntelliJ, right-click the commit in the Git log and choose Revert Commit.
git revert b81d0e4[main 9a7c155] Revert "Add /heal command"Branches in plain words
A branch is a separate line of commits. The default branch is main. When you want to try something big, such as a new menu, you start a branch for it. Your main stays working while you experiment, and you can still fix a bug on main if a player reports one.
git switch -c heal-menuSwitched to a new branch 'heal-menu' git commit -am "Draw the heal menu" git switch mainSwitched to branch 'main' git merge heal-menuUpdating b81d0e4..e40a6d2Fast-forwardgit switch -c heal-menucreates the branch and moves you onto it. Your files change to match that branch.git switch maingoes back. The menu code disappears from your folder, because it only exists onheal-menu. It is not lost.git merge heal-menubrings the menu commits intomain.
In IntelliJ, the current branch name sits in the top bar (or the bottom right corner in older layouts). Click it for New Branch, Checkout and Merge.
If two branches changed the same lines, Git cannot decide which version wins and reports a merge conflict. IntelliJ opens a three-panel window: your version on the left, the other on the right, the result in the middle. Pick the lines you want and press Apply. Conflicts are rare when you work alone, so do not worry about them yet.
GitHub: a safe copy on the internet
Your repo lives on one disk. If that disk dies, so does your history. GitHub keeps a second copy online, free. It also lets you read other people's plugins and work from a second PC.
Make an account
Sign up at github.com. Pick a username you would not mind in a public link.
Connect IntelliJ
Open , press Add account and sign in through the browser.
Share the project
Choose . Type a repository name, leave Private ticked while you learn, and press Share. IntelliJ creates the repo on GitHub and pushes your commits to it.
Push later changes
After new commits, press Ctrl+Shift+K (Push). To get commits made elsewhere, choose .
The terminal version, if you create an empty repo on the GitHub website first, is two commands. The address is shown on the new repo's page. A remote is Git's word for "a copy of this repo somewhere else", and origin is the usual name for your GitHub copy.
git remote add origin https://github.com/YourName/my-plugin.git git push -u origin mainbranch 'main' set up to track 'origin/main'.The first time, Git for Windows opens a browser window so you can sign in. It remembers you afterwards.
Private or public?
- Private: only you (and people you invite) can see it. Choose this while learning, or for a plugin you will sell or run on your own server.
- Public: anyone can read it. Choose this when you want to share, ask for help with a bug, or put the plugin on a download site. You can switch a repo from private to public later in the repo's settings, so starting private costs nothing.
README and license
Add a file named README.md at the top of the repo. GitHub shows it on the repo's front page. Say what the plugin does, which Paper version it needs, and how to install it. A LICENSE file says what other people may do with your code; without one, nobody has permission to reuse it. Releasing and sharing your plugin explains how to pick one in plain words.
Never commit secrets
A secret is anything that gives access to something: a database password, a Discord webhook address, a payment key, an API token. If it is in your repo, anyone who can read the repo can use it. On a public repo that means the entire internet, and bots scan GitHub for leaked keys all day.
The classic mistake looks harmless:
1public final class WebhookSender {2 3 private static final String TOKEN = "d8a1-FAKE-EXAMPLE-ONLY";4}- The secret is now part of the source code, so it is part of every commit, forever.
The fix is to keep the secret out of the code and out of the repo. Read it while the plugin starts, from the server it runs on. This plugin looks in an environment variable first (a named setting that belongs to the operating system, not to your project), and then in the server's config.yml:
Where this file livesgit-github-secretssrcmainjavacomexamplegitgithubsecretsSecretsPlugin.java
The package com.example.gitgithubsecrets is the folder path com/example/gitgithubsecrets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- git-github-secrets/
- src/main/
- java/com/example/gitgithubsecrets/Package com.example.gitgithubsecrets
- SecretsPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- config.ymlDefault settings that server owners can change
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/gitgithubsecrets/Package com.example.gitgithubsecrets
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.gitgithubsecrets;2 3import org.bukkit.plugin.java.JavaPlugin;4 5public final class SecretsPlugin extends JavaPlugin {6 7 @Override8 public void onEnable() {9 saveDefaultConfig();10 String token = findToken();11 if (token.isBlank()) {12 getLogger().warning("No webhook token found. Set webhook-token in plugins/GitSecrets/config.yml");13 return;14 }15 getLogger().info("Webhook token loaded (" + token.length() + " characters).");16 }17 18 private String findToken() {19 String fromEnvironment = System.getenv("GITSECRETS_WEBHOOK_TOKEN");20 if (fromEnvironment != null && !fromEnvironment.isBlank()) {21 return fromEnvironment;22 }23 return getConfig().getString("webhook-token", "");24 }25}- Copies the
config.ymlfrom inside the jar intoplugins/GitSecrets/on the server, but only if it is not there yet. The server owner then edits that copy. - True when the text is empty or only spaces. Check this so a missing secret gives a clear warning, not a crash later.
- The log line prints how long the token is, never the token itself. Console logs get pasted into support chats, so never print a secret.
- Reads an environment variable. Hosting panels and Docker servers often use these for secrets.
- The fallback: the value the server owner typed into
config.yml.
The config.yml inside your project holds only an empty placeholder, so it is safe to commit:
Where this file livesgit-github-secretssrcmainresourcesconfig.yml
Files in src/main/resources are copied into the jar exactly as they are. Paper looks for plugin.yml at the top of the jar.
- git-github-secrets/
- src/main/
- java/com/example/gitgithubsecrets/Package com.example.gitgithubsecrets
- SecretsPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- config.ymlyou are hereDefault settings that server owners can change
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/gitgithubsecrets/Package com.example.gitgithubsecrets
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1webhook-token: ''The real value lives in the server's copy of the file, which is in run/plugins/... on your test server (ignored by Git) and on your real server (not in Git at all).
Mistakes to avoid
- Committing
build/orrun/. Add the.gitignorebefore your first commit. - One giant commit called "update". Commit when one thing works, and describe it.
- Editing before you pull. If you work on two PCs, pull first. Otherwise you get a merge conflict.
- Using
git restoreon work you wanted. Uncommitted changes cannot be brought back. - Pushing a secret, even to a private repo. Repos get shared and made public later.
Your first three commits
Make a practice repo. Start with an empty folder, make three commits, break something, and undo it.
- Create a folder called
git-practice, open PowerShell in it, and rungit init. - Create
notes.txtwith one line of text and commit it with the message "Add notes". - Add a second line and commit "Add second note". Add a third line and commit "Add third note".
- Delete all the text in
notes.txtand save, then use Git to bring the file back. - Look at your history with
git log --oneline. You should see three commits.
Hint 1
Create a file with Set-Content notes.txt "first line" and add a line with Add-Content notes.txt "second line".
Hint 2
After deleting the text, git status shows modified: notes.txt. One command brings the last committed version back.
Show the solution
git restore notes.txt replaces the file with its text from the latest commit.
git initInitialized empty Git repository in C:/Users/you/git-practice/.git/ Set-Content notes.txt "first line" git add . git commit -m "Add notes" Add-Content notes.txt "second line" git commit -am "Add second note" Add-Content notes.txt "third line" git commit -am "Add third note" Clear-Content notes.txt git restore notes.txt git log --onelinec2e9b04 (HEAD -> main) Add third note71d3a55 Add second note08fb6e1 Add notesgit commit -am adds every changed file that Git already tracks and commits in one step. It does not add brand-new files; that is why the first commit used git add ..
Fix the leaked token
A friend sends you the WebhookSender class with TOKEN written into the code, and says "the repo is public". Write down, in order, what you would do, then describe how the plugin should read the token instead.
Hint
The first step is not about code. Think about who can already see the old commits.
Show the solution
- Cancel the old token on the service that issued it and create a new one. Anyone who copied the repo already has the old one.
- Change the code to read the token from the environment or from
config.yml, exactly as the plugin above does. - Put an empty placeholder in the committed
config.ymland paste the new token only into the server's copy.
The plugin code from this page is the answer to step 2. Nothing in the repo contains the new secret.
Recap
- Git saves snapshots (commits) of your project so you can look back and undo mistakes. GitHub keeps a copy online.
- Turn Git on with , then commit with Ctrl+K.
- Commit only files you write. Ignore
.gradle/,build/,run/and.idea/, but keep the Gradle wrapper jar. git restorediscards uncommitted work for good;git revertsafely undoes an old commit.- Branches let you try things without risking
main; merge them when they work. - Start GitHub repos as private. Never put a password, token or webhook address in a commit. If it leaks, cancel it first.
Quick quiz
Which of these should go in your
.gitignore?Generated output belongs in.gitignore. Your code andplugin.ymlare exactly what history should keep; withoutsrc/there would be no plugin to save.You changed a file, did not commit, and ran
git restoreon it. What happens?Uncommitted work is not in Git's history, so there is nothing to bring back. Commit first if you might want it.What is the difference between Git and GitHub?
You can use Git without GitHub at all. GitHub adds online backup and sharing, and can host private repos too.You accidentally pushed a Discord webhook address to a public repo, and then deleted it in a new commit. What should you do?
The old commits still contain the address, and someone may already have copied it. Only canceling it makes it useless.Why use a branch for a big experiment?
A branch is only a separate line of commits. You can commit onmainanytime; a branch just keeps unfinished work out of your way.
Next steps
- Releasing and sharing your plugin: version numbers, licenses, and where to upload your jar.
- Organizing a bigger plugin: keep growing code tidy so commits stay small and clear.
- Using AI assistants well: never paste secrets into a chat, and commit before you try AI-written changes so you can undo them.