Overview
The IPC (Inter-Process Communication) system enables communication between the Electron main process and the React renderer process. All channels are exposed through the window.electronAPI object in the renderer.
IPC channels are only available when running in Electron. Check isElectron from the EditorContext before using these APIs.
Channel Types
There are two types of IPC channels:
Handle channels (ipcMain.handle) - Async request/response pattern, invoked with ipcRenderer.invoke
Send channels (ipcMain.on) - One-way messages, sent with ipcRenderer.send
Handle Channels (Async)
read-directory
Reads the contents of a directory and returns an array of files and folders.
Absolute path to the directory to read
Array of file and directory items, sorted with directories first, then alphabetically Absolute file system path
Whether this item is a directory
const items = await window . electronAPI . readDirectory ( '/Users/username/projects' );
console . log ( items );
// [
// { name: 'src', path: '/Users/username/projects/src', isDirectory: true },
// { name: 'package.json', path: '/Users/username/projects/package.json', isDirectory: false }
// ]
read-file
Reads the contents of a file as a UTF-8 string.
Absolute path to the file to read
File contents as a string, or empty string if read fails
const content = await window . electronAPI . readFile ( '/path/to/file.txt' );
console . log ( content ); // File contents
save-file
Writes content to a file, overwriting existing contents.
Absolute path where the file should be saved
Content to write to the file (UTF-8)
true if save succeeded, false if it failed
const success = await window . electronAPI . saveFile (
'/path/to/file.txt' ,
'Hello, world!'
);
if ( success ) {
console . log ( 'File saved successfully' );
}
get-folder
Retrieves the currently open folder path.
Current folder path, or null if no folder is open
const folder = await window . electronAPI . getFolder ();
if ( folder ) {
console . log ( 'Current workspace:' , folder );
}
Send Channels (One-way)
create-terminal
Creates a new pseudo-terminal (PTY) process. The terminal will emit data via the terminal-data event.
window . electronAPI . createTerminal ();
// Listen for terminal output
window . electronAPI . onTerminalData (( data ) => {
console . log ( 'Terminal output:' , data );
});
Creates a shell process using PowerShell on Windows or the user’s default shell on Unix systems. The working directory defaults to the user’s home directory.
Sends input to the active terminal process.
Input data to send to the terminal (e.g., commands, keystrokes)
// Send a command
window . electronAPI . terminalInput ( 'ls -la \r ' );
// Send Ctrl+C
window . electronAPI . terminalInput ( ' \x03 ' );
Receive Channels (Events)
These channels listen for events from the main process.
folder-opened
Fired when a folder is opened via the File menu.
callback
(path: string) => void
required
Function called with the opened folder path
window . electronAPI . onFolderOpened (( folderPath ) => {
console . log ( 'Opened folder:' , folderPath );
// Load folder contents
});
Fired when the sidebar toggle shortcut (Cmd+B / Ctrl+B) is pressed.
Function called when sidebar should be toggled
window . electronAPI . onToggleSidebar (() => {
console . log ( 'Toggle sidebar' );
setSidebarVisible ( prev => ! prev );
});
toggle-terminal
Fired when the terminal toggle shortcut (Cmd+Shift+T / Ctrl+Shift+T) is pressed.
Function called when terminal should be toggled
window . electronAPI . onToggleTerminal (() => {
console . log ( 'Toggle terminal' );
setTerminalVisible ( prev => ! prev );
});
terminal-data
Fired when the terminal process outputs data.
callback
(data: string) => void
required
Function called with terminal output data
window . electronAPI . onTerminalData (( data ) => {
// Append to terminal display
terminalElement . textContent += data ;
});
ElectronAPI Interface
Complete TypeScript interface for the exposed API:
interface ElectronAPI {
// File system operations
readDirectory : ( dirPath : string ) => Promise < FileItem []>;
readFile : ( filePath : string ) => Promise < string >;
saveFile : ( filePath : string , content : string ) => Promise < boolean >;
getFolder : () => Promise < string | null >;
// Event listeners
onFolderOpened : ( callback : ( path : string ) => void ) => void ;
onToggleSidebar : ( callback : () => void ) => void ;
onToggleTerminal : ( callback : () => void ) => void ;
// Terminal operations
createTerminal : () => void ;
onTerminalData : ( callback : ( data : string ) => void ) => void ;
terminalInput : ( data : string ) => void ;
}
// Available at:
declare global {
interface Window {
electronAPI ?: ElectronAPI ;
}
}
Usage Examples
Complete File Manager
import { useState , useEffect } from 'react' ;
function FileManager () {
const [ folder , setFolder ] = useState < string | null >( null );
const [ files , setFiles ] = useState < FileItem []>([]);
useEffect (() => {
if ( ! window . electronAPI ) return ;
// Listen for folder open events
window . electronAPI . onFolderOpened ( async ( path ) => {
setFolder ( path );
const items = await window . electronAPI ! . readDirectory ( path );
setFiles ( items );
});
// Load initial folder
window . electronAPI . getFolder (). then ( async ( path ) => {
if ( path ) {
setFolder ( path );
const items = await window . electronAPI ! . readDirectory ( path );
setFiles ( items );
}
});
}, []);
const handleFileClick = async ( item : FileItem ) => {
if ( item . isDirectory ) {
const items = await window . electronAPI ! . readDirectory ( item . path );
setFiles ( items );
} else {
const content = await window . electronAPI ! . readFile ( item . path );
console . log ( 'File content:' , content );
}
};
return (
< div >
< h3 >{folder || 'No folder open' } </ h3 >
< ul >
{ files . map ( item => (
< li key = {item. path } onClick = {() => handleFileClick ( item )} >
{ item . isDirectory ? '📁' : '📄' } { item . name }
</ li >
))}
</ ul >
</ div >
);
}
Complete Terminal Component
import { useEffect , useRef , useState } from 'react' ;
function Terminal () {
const [ output , setOutput ] = useState ( '' );
const inputRef = useRef < HTMLInputElement >( null );
useEffect (() => {
if ( ! window . electronAPI ) return ;
// Create terminal session
window . electronAPI . createTerminal ();
// Listen for output
window . electronAPI . onTerminalData (( data ) => {
setOutput ( prev => prev + data );
});
}, []);
const handleCommand = ( e : React . KeyboardEvent ) => {
if ( e . key === 'Enter' && inputRef . current ) {
const command = inputRef . current . value ;
window . electronAPI ?. terminalInput ( command + ' \r ' );
inputRef . current . value = '' ;
}
};
return (
< div >
< pre >{ output } </ pre >
< input
ref = { inputRef }
onKeyDown = { handleCommand }
placeholder = "Enter command..."
/>
</ div >
);
}
Keyboard Shortcuts Handler
import { useEffect } from 'react' ;
import { useEditor } from './lib/editor-context' ;
function KeyboardShortcuts () {
const { setSidebarVisible , setTerminalVisible } = useEditor ();
useEffect (() => {
if ( ! window . electronAPI ) return ;
// Cmd+B / Ctrl+B - Toggle sidebar
window . electronAPI . onToggleSidebar (() => {
setSidebarVisible ( prev => ! prev );
});
// Cmd+Shift+T / Ctrl+Shift+T - Toggle terminal
window . electronAPI . onToggleTerminal (() => {
setTerminalVisible ( prev => ! prev );
});
}, [ setSidebarVisible , setTerminalVisible ]);
return null ; // No UI, just event handlers
}
Security Considerations
The preload script uses contextBridge to safely expose IPC methods to the renderer process. Never disable contextIsolation or enable nodeIntegration in production.
All file system operations are validated in the main process. The renderer cannot:
Access files outside the opened workspace
Execute arbitrary system commands
Access Node.js APIs directly
Error Handling
Handle channels return safe defaults on error:
read-directory returns empty array []
read-file returns empty string ""
save-file returns false
get-folder returns null
try {
const content = await window . electronAPI ! . readFile ( '/nonexistent.txt' );
if ( content === '' ) {
console . log ( 'File could not be read' );
}
} catch ( error ) {
// IPC errors are caught internally
}