Paper Plugin Guide
File mode0

Going further

Git and GitHub for plugin developers

Save every version of your plugin, undo mistakes and share code safely.

Beginner22 min read

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.

Commits on main and on a feature branch The main line has commits First commit, Add /heal and Fix config typo. A feature branch called heal-menu starts after Add /heal with two commits, then is merged back into main with a merge commit. main heal-menu First commit Add /heal Fix config typo Merge heal-menu Draw menu Add clicks git switch -c heal-menu git merge heal-menu HEAD Each circle is a commit: a saved snapshot of your project.
Each circle is a commit. The main line is your plugin's story. A branch is a side line where you try something without touching the main line.

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

  1. Install it with winget

    Open PowerShell and run this. winget is Windows' built-in installer.

    PowerShell
     winget install --id Git.Git -eFound Git [Git.Git] Version 2.51.0Successfully installed

    You can also download the installer from git-scm.com and click through it. The default choices are fine.

  2. 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:

    PowerShell
     git --versiongit version 2.51.0.windows.1
  3. Tell 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.

    PowerShell
     git config --global user.name "Jace" git config --global user.email "you@example.com" git config --global init.defaultBranch main

    Use 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.

  1. Open your plugin project

    Open the project folder as usual (see Anatomy of a plugin project).

  2. Create the repository

    Choose VCSEnable Version Control Integration..., pick Git in the list, and press OK. In some IntelliJ versions the same command is called GitCreate Git Repository.... Behind the scenes IntelliJ runs git init in your project folder.

  3. 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.
    • 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 NewFile and type .gitignore. Each line is a name or pattern. Git acts as if matching files do not exist.

.gitignore
1.gradle/2build/3run/4out/5.idea/6*.iml7.kotlin/
  1. A trailing slash means a folder. Git skips the folder and everything in it.
  2. Your compiled jar lives in build/libs. You share the jar through a release (see Releasing and sharing your plugin), not through commits.
  3. The test server's worlds and logs. They can be hundreds of megabytes, and they may hold player data.
  4. 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:

The Commit window. Tick the files to save, type a message, and press Commit.
  1. Tick the files

    Files under Changes are ticked by default. Check that .gitignore is in the list and that build/, 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.

  2. 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.

  3. 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:

PowerShell
 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 join

git 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.

The four places your code lives with Git Changes move from your project folder to the staging area with git add, into your local history with git commit, and onto GitHub with git push. git pull brings changes back from GitHub. Your code moves left to right in three small steps Project folder files you edit in IntelliJ Staging area changes picked for the next save Local history every commit, on your PC GitHub a copy on the internet add commit push pull: bring newer work back to your PC IntelliJ's Commit window does add and commit in one click.
A change travels from your files, to the staging area, to your history, and finally to GitHub.

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 GitShow History 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:

PowerShell
 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:

PowerShell
 git restore src/main/java/com/example/myplugin/JoinListener.java

Bring 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:

PowerShell
 git restore --source=3f2a9c1 src/main/java/com/example/myplugin/JoinListener.java

The 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.

PowerShell
 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.

PowerShell
 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-forward
  • git switch -c heal-menu creates the branch and moves you onto it. Your files change to match that branch.
  • git switch main goes back. The menu code disappears from your folder, because it only exists on heal-menu. It is not lost.
  • git merge heal-menu brings the menu commits into main.

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.

  1. Make an account

    Sign up at github.com. Pick a username you would not mind in a public link.

  2. Connect IntelliJ

    Open FileSettingsVersion ControlGitHub, press Add account and sign in through the browser.

  3. Share the project

    Choose GitGitHubShare Project on GitHub. 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.

  4. Push later changes

    After new commits, press Ctrl+Shift+K (Push). To get commits made elsewhere, choose GitPull....

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.

PowerShell
 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:

Do not do thisHas a mistake
1public final class WebhookSender {2 3    private static final String TOKEN = "d8a1-FAKE-EXAMPLE-ONLY";4}
  1. 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:

SecretsPlugin.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. Copies the config.yml from inside the jar into plugins/GitSecrets/ on the server, but only if it is not there yet. The server owner then edits that copy.
  2. True when the text is empty or only spaces. Check this so a missing secret gives a clear warning, not a crash later.
  3. 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.
  4. Reads an environment variable. Hosting panels and Docker servers often use these for secrets.
  5. 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:

config.ymlCompiles on Paper 26.3Compile Lab
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
    • 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

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/ or run/. Add the .gitignore before 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 restore on work you wanted. Uncommitted changes cannot be brought back.
  • Pushing a secret, even to a private repo. Repos get shared and made public later.
Try it

Your first three commits

Make a practice repo. Start with an empty folder, make three commits, break something, and undo it.

  1. Create a folder called git-practice, open PowerShell in it, and run git init.
  2. Create notes.txt with one line of text and commit it with the message "Add notes".
  3. Add a second line and commit "Add second note". Add a third line and commit "Add third note".
  4. Delete all the text in notes.txt and save, then use Git to bring the file back.
  5. 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.

PowerShell
 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 notes

git 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 ..

Try it

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
  1. 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.
  2. Change the code to read the token from the environment or from config.yml, exactly as the plugin above does.
  3. Put an empty placeholder in the committed config.yml and 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 VCSEnable Version Control Integration..., then commit with Ctrl+K.
  • Commit only files you write. Ignore .gradle/, build/, run/ and .idea/, but keep the Gradle wrapper jar.
  • git restore discards uncommitted work for good; git revert safely 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

  1. Which of these should go in your .gitignore?

  2. You changed a file, did not commit, and ran git restore on it. What happens?

  3. What is the difference between Git and GitHub?

  4. You accidentally pushed a Discord webhook address to a public repo, and then deleted it in a new commit. What should you do?

  5. Why use a branch for a big experiment?

Next steps