How to build a custom Minecraft launcher for your server
Building a custom launcher is the best way to ensure your players always have the right version and settings. With EML Lib, this process is now accessible to everyone.
This guide walks you through building a minimal Electron launcher using the Agnostic mode (standalone), which doesn’t require any complex backend infrastructure.
Prerequisites
- Node.js v20+
- Basic JavaScript knowledge
Step 1: Initialize the project
mkdir my-launcher && cd my-launcher
npm init -y
npm install eml-lib electron Step 2: The UI (index.html)
Create an index.html file. We keep it simple: a field for the player name and a “Play” button.
<!DOCTYPE html>
<html>
<head>
<title>EML Demo Launcher</title>
<style>
body {
background: #1a1a1a;
color: white;
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
input {
padding: 10px;
margin-bottom: 10px;
border-radius: 4px;
border: none;
width: 200px;
}
button {
padding: 10px 20px;
background: #388e3c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
#status {
margin-top: 20px;
font-size: 0.9em;
color: #888;
}
</style>
</head>
<body>
<h1>Minecraft Launcher</h1>
<input type="text" id="username" placeholder="Player Name" value="Player" />
<button id="play-btn">PLAY</button>
<div id="status">Ready to launch</div>
<script>
const { ipcRenderer } = require('electron')
const playBtn = document.getElementById('play-btn')
const status = document.getElementById('status')
playBtn.addEventListener('click', () => {
const name = document.getElementById('username').value
status.innerText = 'Launching...'
playBtn.disabled = true
ipcRenderer.send('launch-game', name)
})
ipcRenderer.on('log', (event, msg) => {
status.innerText = msg
})
</script>
</body>
</html> Step 3: The main process (main.js)
This is where EML Lib does the magic. Create a main.js file.
const { app, BrowserWindow, ipcMain } = require('electron')
const { Launcher, CrackAuth } = require('eml-lib')
const path = require('path')
function createWindow() {
const win = new BrowserWindow({
width: 400,
height: 500,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
})
win.loadFile('index.html')
return win
}
app.whenReady().then(() => {
const mainWindow = createWindow()
ipcMain.on('launch-game', async (event, username) => {
const auth = new CrackAuth()
const account = auth.auth(username)
const launcher = new Launcher({
root: path.join(app.getPath('userData'), 'game'),
account: account,
minecraft: {
version: '1.20.4'
}
})
launcher.on('launch_download', (d) => mainWindow.webContents.send('log', `Downloading ${d.total.amount} files...`))
launcher.on('launch_launch', () => mainWindow.webContents.send('log', 'Opening Minecraft...'))
launcher.on('launch_close', () => app.quit())
try {
await launcher.launch()
} catch (err) {
mainWindow.webContents.send('log', 'Error: ' + err.message)
}
})
}) Note
The CrackAuth is used here for simplicity. For a real server, you should use MicrosoftAuth (or any other authentication method provided by EML Lib) to ensure players have a valid license.
Tip
- This example uses the Agnostic mode (standalone), which means it doesn’t require any backend server. But you can easily connect it to the EML AdminTool later to manage your mods and configs remotely without updating the launcher itself.
- To help you configure you launcher, you can use Config generator to generate the
Launcheroptions based on your needs.
Step 4: Run it!
Update your package.json to include "main": "main.js" juste under the "name" field and run:
npx electron . Key takeaways
- Authentication: We used
CrackAuthfor simplicity. For a real server, you should useMicrosoftAuthto ensure players have a valid license. - Agnostic mode: Introduced in v2.2.0, it allows you to launch Minecraft (Vanilla or Modded) without any backend server.
- Scalability: While this script is standalone, you can easily connect it to the EML AdminTool later to manage your mods and configs remotely without updating the launcher itself.
The full API documentation is available on here. Happy coding!