Skip to content

Error while importing electron in react | import { ipcRenderer } from 'electron' #9920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Amthieu opened this issue Jul 3, 2017 · 88 comments

Comments

@Amthieu
Copy link

Amthieu commented Jul 3, 2017

I have created a simple react app with create-react-app and I have integrated it with electron successfully. Everything was working great until I tried to import electron inside the action creator file. If I remove the line below, the app works fine. The problem is that I can't use the ipcRenderer to communicate from the react side to the electron main process.

This line causes the app to crash:
import { ipcRenderer } from 'electron';

I get the following error:

TypeError: fs.existsSync is not a function
(anonymous function)
node_modules/electron/index.js:6

  3 | 
  4 | var pathFile = path.join(__dirname, 'path.txt')
  5 | 
> 6 | if (fs.existsSync(pathFile)) {
  7 |   module.exports = path.join(__dirname, fs.readFileSync(pathFile, 'utf-8'))
  8 | } else {
  9 |   throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again')

I found out on Google that this is a common problem when trying to import electron.

Thanks for the help

@Amthieu Amthieu changed the title Error while importing electron in react Error while importing electron in react | import { ipcRenderer } from 'electron' Jul 3, 2017
@MarshallOfSound
Copy link
Member

CRA uses webpack which messes with standard module loading (including fs).

I'd recommend looking into the Electron mode for webpack and ejecting from CRA

@MarshallOfSound
Copy link
Member

GitHub issues are for feature requests and bug reports, questions about using Electron should be directed to the community or to the Slack Channel.

@Amthieu
Copy link
Author

Amthieu commented Jul 8, 2017

@MarshallOfSound my mistake.

I found the solution in issue #7300 if it can help anyone.

const { ipcRenderer } = window.require('electron');

Please note that this will work when you run the Electron app, but if you just want to test your React code inside the browser it will still crash (window.require is not defined in the browser as it is in Electron).

@ciriousjoker
Copy link

If you want to access app.quit(), you can use this:

const { app } = window.require('electron').remote;

Maybe it helps someone...

@hendrixroa
Copy link

hendrixroa commented Aug 16, 2017

@ciriousjoker these is solutions, thanks!

@holgersindbaek
Copy link

I'm still getting window.require is not a function. I'm using Electron with React Starter Kit (https://github.com/kriasoft/react-starter-kit). Everything is working nicely, except this.

I've set my Electron app to load my app from the web, so the app is not running locally:
https://gist.github.com/holgersindbaek/68f6db82f507967a51ca75c527faeff6

What I'm trying to do, is call the ipcRenderer in one of my React files. I'm not sure if it's even possible when my app is being loaded from the web though. Any suggestions?

@HemalR
Copy link

HemalR commented Oct 14, 2017

@holgersindbaek

In the same boat as you... Did you find a solution?

@holgersindbaek
Copy link

No. I'm pretty sure it's not possible to load the ipcRenderer from the browser.

@Amthieu
Copy link
Author

Amthieu commented Oct 14, 2017

If you are running your React app in the browser it won't work. Run it inside Electron and you should be fine.

@holgersindbaek
Copy link

@Amthieu Thanks for the advice. I'm still in doubt as to how I can make my React project (based on React Starter Kit) run in Electron. Any advice would be greatly appreciated:

https://discuss.atom.io/t/getting-electron-to-work-with-react-starter-kit/48594

@HemalR
Copy link

HemalR commented Oct 16, 2017

Right, I have a solution.

  1. Create a preload.js file with the code:
window.ipcRenderer = require('electron').ipcRenderer;
  1. Preload this file in your main.js via webPreferences:
  mainWindow = new BrowserWindow({
    width: 800, 
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      preload: __dirname + '/preload.js'
    }
  });
  1. Now, you will have access from your react app. E.g. this will work:
componentDidMount() {
		if (isElectron()) {
			console.log(window.ipcRenderer);
			window.ipcRenderer.on('pong', (event, arg) => {
				this.setState({ipc: true})
			})
			window.ipcRenderer.send('ping')
		}
	}

Note - using this: https://github.com/cheton/is-electron for the isElectron() function

@astrotars
Copy link

astrotars commented Dec 1, 2017

@HemalR Step 3 should be the following (now):

componentDidMount() {
	if (window.isElectron) {
		console.log(window.ipcRenderer);
		window.ipcRenderer.on('pong', (event, arg) => {
			this.setState({ipc: true})
		})
		window.ipcRenderer.send('ping')
	}
}

Note: window.isElectron is not a function.

@HemalR
Copy link

HemalR commented Dec 2, 2017

@nparsons08

Apologies - should have added where I am getting isElectron from, have edited my code example with the link to: https://github.com/cheton/is-electron

@sandywk
Copy link

sandywk commented Jun 4, 2018

@holgersindbaek
Is there a solution now

@carlosdelfino
Copy link

for me only work if nodeIntegration is true;

webPreferences: {
      nodeIntegration: true, 
      preload: __dirname + '/preload.js'
}

@gino8080
Copy link

gino8080 commented Sep 1, 2018

work great the @HemalR solution!

now HOW to send FROM electron TO React?

tried with
on electron side

 ipcMain.emit("pong", "Hello!"); 

but nothing got received from the React listener

window.ipcRenderer.on("pong", (event, arg) => {
        console.log("PONG");
});

is correct to use ipcMain.emit() or should I use something else?

@gino8080
Copy link

gino8080 commented Sep 1, 2018

ok just found I have to use (on the electron main process)

mainWindow.webContents.send("pong", "Hello!");

thank you to all!

@chchmatt
Copy link

chchmatt commented Nov 4, 2018

I tried all of the above to no avail. What worked for me was a giant hack. Modify the file ./node_modules/electron/index.js and hard code the path to electron.exe

e.g.

function getElectronPath() {
  return 'D:\\Repos\\MyProject\\node_modules\\electron\\dist\\electron.exe';
}

module.exports = getElectronPath();

@cyclonstep
Copy link

Wow, I couldn't get the IPCRenderer working on my React Components. I've tried all of the method above. Did any of you incidentally have any hints that I can use for it to be working? thanks

@HemalR
Copy link

HemalR commented Nov 19, 2018

Hmmm... My electron app still works just fine using my solution above - but I haven't updated it in a few months now (haven't needed to).

I wonder if there is a breaking change that would stop this from working? Maybe you guys can post your electron versions?

@cyclonstep Is there any specific error you were getting? Hard to help without a code snippet or some logs...

@nocke
Copy link

nocke commented Nov 19, 2018

I am using parcel for bundling.
Window.require also solved it for me (also showing, what didn't):

import Vue from 'vue/dist/vue.min'
import App from './App'

// BAD? import { ipcRenderer } from 'electron'
// BAD? const { ipcRenderer } = require('electron')
// GOOD:
const { ipcRenderer } = window.require('electron')

( further below in the same file is the electron „pong-Demo“, which kinda prooves, it works)

Perhaps noteworthy: Even when doing it wrong, bundle size does not grow (compare to without the electron-require. This is so far my first&so far only render-side electron import) by the entire Electron size or such, but only by around 20kb, which seems to be some shim/wrapper code on its own, coming from node_modules/electron-download/node_modules/debug/dist/debug.js:242:ff...

2: [function (require, module, exports) {
  // shim for using process in browser
  var process = module.exports = {}; // cached from whatever global is present so that test runners that
 stub it

Anyway, things work as said above.

Node version 10.2.0
Chrome version 66.0.3359.181
Electron version 3.0.2

@kmwhelan93
Copy link

window.require wasn't working for me in my main script with error window is not defined, so I switched to const electron = eval('require')("electron"). Hope this helps someone. Using webpack, and problem was that webpack was evaluating my require statement at compilation time.

@moshfeu
Copy link
Contributor

moshfeu commented Dec 13, 2018

@MarshallOfSound my mistake.

I found the solution in issue #7300 if it can help anyone.

const { ipcRenderer } = window.require('electron');

Please note that this will work when you run the Electron app, but if you just want to test your React code inside the browser it will still crash (window.require is not defined in the browser as it is in Electron).

And for typescript:

import {IpcRenderer} from 'electron';

declare global {
  interface Window {
    require: (module: 'electron') => {
      ipcRenderer: IpcRenderer
    };
  }
}

const { ipcRenderer } = window.require('electron');

@stylejy
Copy link

stylejy commented Jan 15, 2019

@moshfeu Your solution works fantastic. I don't need Webpack or Browserfy to use IpcRenderer in my React project. Thanks so much again :D

@marksyzm
Copy link

marksyzm commented Feb 28, 2019

For typescript and using @HemalR 's example from above but WITHOUT nodeIntegration: true: #9920 (comment):

Right, I have a solution.

  1. Create a preload.js file with the code:
window.ipcRenderer = require('electron').ipcRenderer;
  1. Preload this file in your main.js via webPreferences:
  mainWindow = new BrowserWindow({
    width: 800, 
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      preload: __dirname + '/preload.js'
    }
  });
  1. Now, you will have access from your react app. E.g. this will work:
componentDidMount() {
		if (isElectron()) {
			console.log(window.ipcRenderer);
			window.ipcRenderer.on('pong', (event, arg) => {
				this.setState({ipc: true})
			})
			window.ipcRenderer.send('ping')
		}
	}

Note - using this: https://github.com/cheton/is-electron for the isElectron() function

combined with
#9920 (comment)

I used this:

import { IpcRenderer } from 'electron';

declare global {
  interface Window {
    ipcRenderer: IpcRenderer
  }
}

export const { ipcRenderer } = window;

Hope that helps someone out there! Works with stenciljs, and I imagine react and angular

@dione2017
Copy link

dione2017 commented Apr 2, 2019

just add target: "electron-renderer" in webpack configs.
export default {
...
target: "electron-renderer"
...
}

@reZach
Copy link

reZach commented Jan 18, 2020

2022 edit

I've posted a new history / step-by-step understanding of the Electron framework and good secure coding practices with the framework as it stands today as an additional resource here. This resource is a good resource for beginners or those who are not familiar with the Electron framework.

Original reply

I hope that this comment get noticed, because a lot of people are asking about importing fs or ipcRenderer in your apps. It's a common-need for electron apps but I found not many people have got it right, and are using outdated patterns. tl;dr - there is a security vulnerability if you don't import your node module (ie. fs) or electron module (ie. ipcRenderer) in the correct way. If you are using your app for only yourself you are probably safe, but if you ever want to share or sell your app you should read ahead.

Our goal

Before I go into the solution, it's important to understand why we are doing this in the first place. Electron apps allow us to include node modules in our apps, which gives them amazing power, but security concerns. We want to allow our app to use native-os (ie. node) features, but we don't want them to be abused.

As brought up by @raddevus in a comment, this is necessary when loading remote content. If your electron app is entirely offline/local, then you are probably okay simply turning on nodeIntegration:true. I still would, however, opt to keep nodeIntegration:false to act as a safeguard for accidental/malicious users using your app, and prevent any possible malware that might ever get installed on your machine from interacting with your electron app and using the nodeIntegration:true attack vector (incredibly rare, but could happen)!

The easy way

Setting nodeIntegration: true in your BrowserWindow gives your renderer process access to node modules. Doing this, is vulnerable. You have access to require("fs") and require("electron"), but that means if someone were to find a XSS vulnerability, they could run any command you've exposed in your renderer process.

Think deleting all of the files on your computer, or something else that's really bad.

The (alternative) easy way

Alongside setting nodeIntegration to true, it's likely that your app is using webpack to bundle application files. Webpack messes up with certain symbols, so settings like target: 'electron-renderer' or webpack externals allows you to pass through these variables (ipcRenderer) into your app instead.

Still, this changes nothing except how you are setting up your app.

The (other-alternative) easy way

You can use the remote module which gives you access to ipcRenderer. It's basically 'The easy way' in a different form. It's not recommended by Electron's security recommendations to do this since this type of attack suffers from a prototype pollution vector.

Ie. using remote could allow someone to modify a js-object's prototype and wreck havoc on your machine/app.

The almost right way

@marksyzm has a better solution, although not perfect, where we use IPC to send the ipcRenderer to the renderer process. This type of setup is also vulnerable to prototype pollution attacks. If you want to get your app 80% of the way there, I'd use this method, as it probably won't require you to do much refactoring.

The right way

The right way of importing your fs/ipcRenderer into your renderer process is with IPC (inter-process-communication). This is Electron's way of allowing you to talk between main and renderer process. Broken down, this is how your app needs to look:

  1. A BrowserWindow has a preload property. This property is a js file that loads with access to require (which means you can require ipcRenderer)
  2. Your BrowserWindow will also have contextIsolation: true to prevent prototype pollution attacks, but this means you need to use the contextBridge to pass the ipcRenderer to your renderer process
  3. Using the preload script and the contextBridge, you allow your renderer process to access the ipcRenderer
  4. In your main script, you create listeners for the ipcRenderer (in the ipcMain module). Within these listeners you can use the fs module

Roughly this is what all these steps look like:

main.js

const {
  app,
  BrowserWindow,
  ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;

async function createWindow() {

  // Create the browser window.
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false, // is default value after Electron v5
      contextIsolation: true, // protect against prototype pollution
      enableRemoteModule: false, // turn off remote
      preload: path.join(__dirname, "preload.js") // use a preload script
    }
  });

  // Load app
  win.loadFile(path.join(__dirname, "dist/index.html"));

  // rest of code..
}

app.on("ready", createWindow);

ipcMain.on("toMain", (event, args) => {
  fs.readFile("path/to/file", (error, data) => {
    // Do something with file contents

    // Send result back to renderer process
    win.webContents.send("fromMain", responseObj);
  });
});

preload.js

const {
    contextBridge,
    ipcRenderer
} = require("electron");

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
    "api", {
        send: (channel, data) => {
            // whitelist channels
            let validChannels = ["toMain"];
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, data);
            }
        },
        receive: (channel, func) => {
            let validChannels = ["fromMain"];
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender` 
                ipcRenderer.on(channel, (event, ...args) => func(...args));
            }
        }
    }
);

index.html

<!doctype html>
<html lang="en-US">
<head>
    <meta charset="utf-8"/>
    <title>Title</title>
</head>
<body>
    <script>
        window.api.receive("fromMain", (data) => {
            console.log(`Received ${data} from main process`);
        });
        window.api.send("toMain", "some data");
    </script>
</body>
</html>

At the very least, I believe you need electron v7 for these features.

How do I know this?

I care about secure electron apps, and built secure-electron-template in order to create an electron application template to bake-in security instead of thinking of security as an afterthought.

@MarMun
Copy link

MarMun commented Apr 22, 2021

This is what I came up with today because I need to use BrowserWindow.

webSecurity: true,
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,

I organize my api concerns into topic dedicated modules.

In it, I have my functions to 'do stuff'. These can be called from preload and from main context.

The renderer process can access it via ...

window.api.topic.doStuff(args)

... and the main context (triggered e.g. by context-menu) via a simple:

win.webContents.on('context-menu', (_e, args) => {
  topic.doStuff(args)
})

The topic module itself:

electron.topic.js

'use strict'

import { ipcMain, ipcRenderer, BrowserWindow } from 'electron'

// ----------------------------------------------------------------
// MAIN CONTEXT
// ----------------------------------------------------------------

function init () {
  ipcMain.on('topic:doStuff', (event, args) => {
    doStuff(args)
  })
}

// ----------------------------------------------------------------
// END MAIN CONTEXT
// ----------------------------------------------------------------

// ----------------------------------------------------------------
// PRELOAD CONTEXT
// ----------------------------------------------------------------

function doStuff (args) {
  if (!BrowserWindow) {
    // switch to main context
    ipcRenderer.send('topic:doStuff', args)
    return
  }

  const win = BrowserWindow
    .getFocusedWindow()

  // do stuff with current window
}

// ----------------------------------------------------------------
// END PRELOAD CONTEXT
// ----------------------------------------------------------------

const api = { doStuff }

export default { init, api }

in main I init topic module(s):

main.js

'use strict'

import { app } from 'electron'

import topic from './electron.topic.js'

app.on('ready', async () => {
  topic.init() // <--------- init @ main context

  // Create app window etc...
})

and finally preload script

'use strict'

import { contextBridge } from 'electron'
import topic from './electron.topic.js'

contextBridge.exposeInMainWorld(
  'api',
  {
    platform: process.platform,
    topic: { ...topic.api },
  }
)

@TulshiDas39
Copy link

TulshiDas39 commented Aug 13, 2021

The above solutions did not work for me.

Note: I am using typescript
I managed to work by the following steps:

  1. in preload.ts file
const renderer = window.require('electron').ipcRenderer;

window.addEventListener("DOMContentLoaded", () => {
  window.ipcRenderer = renderer;
  
});

  1. in react app
    In App.tsx compoent
function App() {
  const [start,setState] = useState(false);
  useEffect(()=>{
    let timer = setInterval(()=>{
      if(!!window.ipcRenderer) {
        clearInterval(timer);
        setState(true);
      }
    },10);
  })
  if(!start){
    return <div></div>;
  }
  return (
    <BrowserRouter>
        <Layout />
    </BrowserRouter>
  );
}

export default App;

@magom001
Copy link

magom001 commented Aug 15, 2021

I used https://www.electronforge.io/templates/typescript-+-webpack-template to setup an electron project. None of the solutions above seem to work. Unable to import electron from react.

An insecure solution (https://stackoverflow.com/questions/56091343/typeerror-window-require-is-not-a-function/56095485). I wanted to use typeorm with sqlite without signaling through ipc.

...
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true,
      contextIsolation: false,
      preload: path.resolve('src', 'preload.js')
    }
...
  (global as any).Database = new Database();
...

Database is a class that instantiates a connection to the sqlite database. In essence:

export class Database {
    private connection: Connection;

    constructor() {
        this.init();
    }

    public async init() {
        this.connection = await createConnection({
            type: 'sqlite',
            database: path.resolve('./data.sqlite3'),
            entities: [<Your entities>]
        });
    }

    public get <ENTITY>Repository() {
        return this.connection.getRepository(<ENTITY>);
    }

}

From within react:

const { remote } = window.require('electron');

After which this should produce a valid output:

    useEffect(() => {
        const db: Database = remote.getGlobal("Database");

        const getEntities = async () => {
            const entities = await db.<ENTITY>Repository.find();

            console.log('entities', entities);
        }

        getEntities();
    }, [])

Hope somebody will find it useful.

@serg06
Copy link

serg06 commented Aug 18, 2021

just add target: "electron-renderer" in webpack configs.
export default {
...
target: "electron-renderer"
...
}

Got this working with craco, here's my craco.config.js:

module.exports = {
    webpack: {
        configure: {
            target: 'electron-renderer'
        }
    }
}

@liwuchen
Copy link

const [start,setState] = useState(false);
useEffect(()=>{
let timer = setInterval(()=>{
if(!!window.ipcRenderer) {
clearInterval(timer);
setState(true);
}
},10);
})
if(!start){
return

;
}

I am trying your solution, but I don't understand, where do you use preload.ts?

@thany
Copy link

thany commented Oct 19, 2021

Here's what I did: I used the preload.js trick from above, but in there I placed this code:

// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge, ipcRenderer } = require("electron");

// As an example, here we use the exposeInMainWorld API to expose the IPC renderer 
// to the main window. They'll be accessible at "window.ipcRenderer".
process.once("loaded", () => {
  contextBridge.exposeInMainWorld("ipcRenderer", ipcRenderer);
});

This will make window.ipcRenderer available in the app.

There is, however, one thing I don't understand. Why can't Electron just make it bloody work?! How hard can it be to expose some stuff into javascript? Why do we have to jump trough a billion hoops to get foundational functionality working?? I have just spent 2 goddamn hours gettings this to work. I could have have been infinitely more productive without this nonsense.


Oh and I forgot to add, place this in a sensible file somewhere if you're using Typescript:

import { IpcRenderer } from 'electron';

declare global {
  interface Window {
    ipcRenderer: IpcRenderer
  }
}

Because I am using Typescript, and this makes it typed. Obviously you will be needing to do this for each thing you expose through the context bridge.

@nfcopier
Copy link

nfcopier commented Nov 17, 2021

For anyone new coming to this thread, @reZach has a comprehensive answer with various solutions (from easy to best) that outline what they are and what the vulnerabilities are.

To summarize the security vulnerability:
If you are only using your own code, you are probably fine and can use the easy solutions. If you're importing third-party code (such as jQuery) from a CDN, you could be exposed to XSS attacks.

@iyobo
Copy link

iyobo commented Mar 17, 2022

The issue with this forced ContextBridge pattern is that we can no longer use MessagePorts.

@OldManMeta
Copy link

process.once("loaded", () => { contextBridge.exposeInMainWorld("ipcRenderer", ipcRenderer); });

@thany thank you very much for this answer. Having pulled hair out with this pile of junk for so long, your solution indeed provided a fix for TS - the contextBridge exposure was the key.

@OrozcoOscar
Copy link

OrozcoOscar commented Feb 17, 2023

This worked for me

mainWindow = new BrowserWindow({
    width: 700, 
    height: 680, 
    
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false // add this
    }
  });

And then you can use in the react component
const { ipcRenderer } = window.require('electron');

aaron-kirk added a commit to Quantum-Interns-at-Qualcomm-Institiute/Quantum-Video-Chat that referenced this issue Apr 23, 2024
slight adjustments to file structure. changed webpack configuration to support mp4. when i copied the session screen over, received error locating 'fs'; unsure if this is the same issue, but i was able to quickly resolve it by replace require('electron') with window.require()

electron/electron#9920
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

reZach commented • edited 2022 edit I've posted a new history / step-by-step understanding of the Electron framework and good secure coding practices with the framework as it stands today as an additional resource here. This resource is a good resource for beginners or those who are not familiar with the Electron framework. Original reply I hope that this comment get noticed, because a lot of people are asking about importing fs or ipcRenderer in your apps. It's a common-need for electron apps but I found not many people have got it right, and are using outdated patterns. tl;dr - there is a security vulnerability if you don't import your node module (ie. fs) or electron module (ie. ipcRenderer) in the correct way. If you are using your app for only yourself you are probably safe, but if you ever want to share or sell your app you should read ahead. Our goal Before I go into the solution, it's important to understand why we are doing this in the first place. Electron apps allow us to include node modules in our apps, which gives them amazing power, but security concerns. We want to allow our app to use native-os (ie. node) features, but we don't want them to be abused. As brought up by @raddevus in a comment, this is necessary when loading remote content. If your electron app is entirely offline/local, then you are probably okay simply turning on nodeIntegration:true. I still would, however, opt to keep nodeIntegration:false to act as a safeguard for accidental/malicious users using your app, and prevent any possible malware that might ever get installed on your machine from interacting with your electron app and using the nodeIntegration:true attack vector (incredibly rare, but could happen)! The easy way Setting nodeIntegration: true in your BrowserWindow gives your renderer process access to node modules. Doing this, is vulnerable. You have access to require("fs") and require("electron"), but that means if someone were to find a XSS vulnerability, they could run any command you've exposed in your renderer process. Think deleting all of the files on your computer, or something else that's really bad. The (alternative) easy way Alongside setting nodeIntegration to true, it's likely that your app is using webpack to bundle application files. Webpack messes up with certain symbols, so settings like target: 'electron-renderer' or webpack externals allows you to pass through these variables (ipcRenderer) into your app instead. Still, this changes nothing except how you are setting up your app. The (other-alternative) easy way You can use the remote module which gives you access to ipcRenderer. It's basically 'The easy way' in a different form. It's not recommended by Electron's security recommendations to do this since this type of attack suffers from a prototype pollution vector. Ie. using remote could allow someone to modify a js-object's prototype and wreck havoc on your machine/app. The almost right way @marksyzm has a better solution, although not perfect, where we use IPC to send the ipcRenderer to the renderer process. This type of setup is also vulnerable to prototype pollution attacks. If you want to get your app 80% of the way there, I'd use this method, as it probably won't require you to do much refactoring. The right way The right way of importing your fs/ipcRenderer into your renderer process is with IPC (inter-process-communication). This is Electron's way of allowing you to talk between main and renderer process. Broken down, this is how your app needs to look: A BrowserWindow has a preload property. This property is a js file that loads with access to require (which means you can require ipcRenderer) Your BrowserWindow will also have contextIsolation: true to prevent prototype pollution attacks, but this means you need to use the contextBridge to pass the ipcRenderer to your renderer process Using the preload script and the contextBridge, you allow your renderer process to access the ipcRenderer In your main script, you create listeners for the ipcRenderer (in the ipcMain module). Within these listeners you can use the fs module Roughly this is what all these steps look like: main.js const { app, BrowserWindow, ipcMain } = require("electron"); const path = require("path"); const fs = require("fs"); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let win; async function createWindow() { // Create the browser window. win = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: false, // is default value after Electron v5 contextIsolation: true, // protect against prototype pollution enableRemoteModule: false, // turn off remote preload: path.join(__dirname, "preload.js") // use a preload script } }); // Load app win.loadFile(path.join(__dirname, "dist/index.html")); // rest of code.. } app.on("ready", createWindow); ipcMain.on("toMain", (event, args) => { fs.readFile("path/to/file", (error, data) => { // Do something with file contents // Send result back to renderer process win.webContents.send("fromMain", responseObj); }); }); preload.js const { contextBridge, ipcRenderer } = require("electron"); // Expose protected methods that allow the renderer process to use // the ipcRenderer without exposing the entire object contextBridge.exposeInMainWorld( "api", { send: (channel, data) => { // whitelist channels let validChannels = ["toMain"]; if (validChannels.includes(channel)) { ipcRenderer.send(channel, data); } }, receive: (channel, func) => { let validChannels = ["fromMain"]; if (validChannels.includes(channel)) { // Deliberately strip event as it includes `sender` ipcRenderer.on(channel, (event, ...args) => func(...args)); } } } ); index.html <!doctype html> <html lang="en-US"> <head> <meta charset="utf-8"/> <title>Title</title> </head> <body> <script> window.api.receive("fromMain", (data) => { console.log(`Received ${data} from main process`); }); window.api.send("toMain", "some data"); </script> </body> </html> At the very least, I believe you need electron v7 for these features. How do I know this? I care about secure electron apps, and built secure-electron-template in order to create an electron application template to bake-in security instead of thinking of security as an afterthought. 👍 182 😄 12 🎉 22 ❤️ 59 🚀 32 👀 3