Building your First App
This is part 2 of the Electron tutorial.
Learning goals
In this part of the tutorial, you will learn how to set up your Electron project and write a minimal starter application. By the end of this section, you should be able to run a working Electron app in development mode from your terminal.
Setting up your project
If you are on a Windows machine, please do not use Windows Subsystem for Linux (WSL) when following this tutorial as you will run into issues when trying to execute the application.
Initializing your npm project
Electron apps are scaffolded using npm, with the package.json file
as an entry point. Start by creating a folder and initializing an npm package
within it with npm init.
- npm
- Yarn
mkdir my-electron-app && cd my-electron-app
npm init
mkdir my-electron-app && cd my-electron-app
yarn init
This command will prompt you to configure some fields in your package.json. There are a few rules to follow for the purposes of this tutorial:
- entry point should be
main.js(you will be creating that file soon). - author, license, and description can be any value, but will be necessary for packaging later on.
node_modules folderElectron's packaging toolchain requires the node_modules folder to be physically on disk in the
way that npm installs Node dependencies. By default, Yarn Berry and
pnpm both use alternative installation strategies.
Therefore, you must set nodeLinker: node-modules
in Yarn or nodeLinker: hoisted in pnpm if you are using
those package managers.
Then, install Electron into your app's devDependencies, which is the list of external development-only package dependencies not required in production.
This may seem counter-intuitive since your production code is running Electron APIs. Under the hood, Electron's JavaScript API binds to a binary that contains its implementations. The packaging step for Electron handles the bundling of this binary, eliminating the need to specify it as a production dependency.
- npm
- Yarn
npm install electron --save-dev
yarn add electron --dev
Your package.json file should look something like this after initializing your package
and installing Electron. You should also now have a node_modules folder containing
the Electron executable, as well as a package-lock.json lockfile that specifies
the exact dependency versions to install.
{
"name":"my-electron-app",
"version":"1.0.0",
"description":"Hello World!",
"main":"main.js",
"scripts":{
"test":"echo \"Error: no test specified\" && exit 1"
},
"author":"Jane Doe",
"license":"MIT",
"devDependencies":{
"electron":"23.1.3"
}
}
If installing Electron directly fails, please refer to our Advanced Installation documentation for instructions on download mirrors, proxies, and troubleshooting steps.
Adding a .gitignore
The .gitignore file specifies which files and directories to avoid tracking
with Git. You should place a copy of GitHub's Node.js gitignore template
into your project's root folder to avoid committing your project's node_modules folder.
Running an Electron app
Read Electron's process model documentation to better understand how Electron's multiple processes work together.
The main script you defined in package.json is the entry point of any
Electron application. This script controls the main process, which runs in a Node.js
environment and is responsible for controlling your app's lifecycle, displaying native
interfaces, performing privileged operations, and managing renderer processes
(more on that later).
Before creating your first Electron app, you will first use a trivial script to ensure your
main process entry point is configured correctly. Create a main.js file in the root folder
of your project with a single line of code:
console.log('Hello from Electron 👋')
Because Electron's main process is a Node.js runtime, you can execute arbitrary Node.js code
with the electron command (you can even use it as a REPL). To execute this script,
add electron . to the start command in the scripts
field of your package.json. This command will tell the Electron executable to look for the main
script in the current directory and run it in dev mode.
{
"name":"my-electron-app",
"version":"1.0.0",
"description":"Hello World!",
"main":"main.js",
"scripts":{
"start":"electron .",
"test":"echo \"Error: no test specified\" && exit 1"
},
"author":"Jane Doe",
"license":"MIT",
"devDependencies":{
"electron":"23.1.3"
}
}
- npm
- Yarn
npm run start
yarn run start
Your terminal should print out Hello from Electron 👋. Congratulations,
you have executed your first line of code in Electron! Next, you will learn
how to create user interfaces with HTML and load that into a native window.
Loading a web page into a BrowserWindow
In Electron, each window displays a web page that can be loaded either from a local HTML
file or a remote web address. For this example, you will be loading in a local file. Start
by creating a barebones web page in an index.html file in the root folder of your project:
<!DOCTYPEhtml>
<html>
<head>
<metacharset="UTF-8"/>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
</body>
</html>
Now that you have a web page, you can load it into an Electron BrowserWindow.
Replace the contents of your main.js file with the following code. We will explain each
highlighted block separately.
const{ app,BrowserWindow}=require('electron')
constcreateWindow=()=>{
const win =newBrowserWindow({
width:800,
height:600
})
win.loadFile('index.html')
}
app.whenReady().then(()=>{
createWindow()
})
Importing modules
const{ app,BrowserWindow}=require('electron')
In the first line, we are importing two Electron modules with CommonJS module syntax:
- app, which controls your application's event lifecycle.
- BrowserWindow, which creates and manages app windows.
Module capitalization conventions
You might have noticed the capitalization difference between the app and BrowserWindow modules. Electron follows typical JavaScript conventions here, where PascalCase modules are instantiable class constructors (e.g. BrowserWindow, Tray, Notification) whereas camelCase modules are not instantiable (e.g. app, ipcRenderer, webContents).
Typed import aliases
For better type checking when writing TypeScript code, you can choose to import
main process modules from electron/main.
const{ app,BrowserWindow}=require('electron/main')
For more information, see the Process Model docs.
ECMAScript modules (i.e. using import to load a module)
are supported in Electron as of Electron 28. You can find more information about the
state of ESM in Electron and how to use them in our app in our ESM guide.
Writing a reusable function to instantiate windows
The createWindow() function loads your web page into a new BrowserWindow instance:
constcreateWindow=()=>{
const win =newBrowserWindow({
width:800,
height:600
})
win.loadFile('index.html')
}
Calling your function when the app is ready
app.whenReady().then(()=>{
createWindow()
})
Many of Electron's core modules are Node.js event emitters that adhere to Node's asynchronous event-driven architecture. The app module is one of these emitters.
In Electron, BrowserWindows can only be created after the app module's ready event
is fired. You can wait for this event by using the app.whenReady() API and
calling createWindow() once its promise is fulfilled.
You typically listen to Node.js events by using an emitter's .on function.
+ app.on('ready', () => {
- app.whenReady().then(() => {
createWindow()
})
However, Electron exposes app.whenReady() as a helper specifically for the ready event to
avoid subtle pitfalls with directly listening to that event in particular.
See electron/electron#21972 for details.
At this point, running your Electron application's start command should successfully
open a window that displays your web page!
Each web page your app displays in a window will run in a separate process called a renderer process (or simply renderer for short). Renderer processes have access to the same JavaScript APIs and tooling you use for typical front-end web development, such as using webpack to bundle and minify your code or React to build your user interfaces.
Managing your app's window lifecycle
Application windows behave differently on each operating system. Rather than enforce these conventions by default, Electron gives you the choice to implement them in your app code if you wish to follow them. You can implement basic window conventions by listening for events emitted by the app and BrowserWindow modules.
Checking against Node's process.platform variable can help you
to run code conditionally on certain platforms. Note that there are only three
possible platforms that Electron can run in: win32 (Windows), linux (Linux),
and darwin (macOS).
Quit the app when all windows are closed (Windows & Linux)
On Windows and Linux, closing all windows will generally quit an application entirely.
To implement this pattern in your Electron app, listen for the app module's
window-all-closed event, and call app.quit()
to exit your app if the user is not on macOS.
app.on('window-all-closed',()=>{
if(process.platform!=='darwin') app.quit()
})
Open a window if none are open (macOS)
In contrast, macOS apps generally continue running even without any windows open. Activating the app when no windows are available should open a new one.
To implement this feature, listen for the app module's activate
event, and call your existing createWindow() method if no BrowserWindows are open.
Because windows cannot be created before the ready event, you should only listen for
activate events after your app is initialized. Do this by only listening for activate
events inside your existing whenReady() callback.
app.whenReady().then(()=>{
createWindow()
app.on('activate',()=>{
if(BrowserWindow.getAllWindows().length===0)createWindow()
})
})