Station 10 · Lesson 4
Writing an EEGLAB plugin
eeglab_data.set (D1) · Nothing yet: the data as downloaded.Download the data (opens in a new tab)Script to reach this state
[ALLEEG, EEG, CURRENTSET, ALLCOM] = eeglab;
eeglabRoot = fileparts(which('eeglab'));
EEG = pop_loadset('eeglab_data.set', fullfile(eeglabRoot, 'sample_data'));
% Show the dataset in the main EEGLAB window
[ALLEEG, EEG, CURRENTSET] = pop_newset(ALLEEG, EEG, 0, 'setname', 'D1 raw', 'gui', 'off');
eeglab redraw;Your browser's back button takes you to the exact place you were, so open anything you like.
The idea in brief
Almost every in EEGLAB comes from a plugin: DIPFIT, ICLabelA tool that classifies each ICA component into seven classes with a probability for each. and firfilt are all plugins. If you write a tool you use in every project, turning it into a pluginA package that adds new functions or menus to EEGLAB. makes it a menu item like any official tool, and lets your colleagues use it the same way.
Before and after
Image coming soon.
Two screenshots side by side. Left: the plugins folder inside the EEGLAB folder, holding the chanvar1.0 folder with the two files eegplugin_chanvar.m and pop_chanvar.m. Right: the EEGLAB Plot menu open, ending with the new Channel variance item below a separator line. Source: screenshot from EEGLAB 2026.1 on the project owner’s computer.
Your next questions, each answered in its own section:
Why do I need this step?
The reasons in detail, and the terms you need
A plugin is simpler than you might expect. It is a folder inside the EEGLAB plugins folder, holding two files:
- A file whose name starts with
eegplugin_. EEGLAB runs it at every startup, and its only job is to add the item to the menu. - A function whose name starts with
pop_. This is the tool itself: it opens a window when arguments are missing, and it returns the command string that reproduces the same result, so it appears in the command historyThe list of commands the GUI has run, shown by the eegh command..
The second rule is what makes your tool part of your scriptA MATLAB file of commands that runs processing steps in order.. Every click on your item appears in eegh as a line of code, as you learned in the command history lesson.
In this lesson we write a plugin called chanvar: it plots the standard deviation of each channelThe signal recorded from one electrode, as shown in EEGLAB. and colors in red the channels far from the rest. A quick check tool, before or after the bad channels lesson.
Why do I need this step?
A plugin is simpler than you might expect. It is a folder inside the EEGLAB plugins folder, holding two files:
- A file whose name starts with
eegplugin_. EEGLAB runs it at every startup, and its only job is to add the item to the menu. - A function whose name starts with
pop_. This is the tool itself: it opens a window when arguments are missing, and it returns the command string that reproduces the same result, so it appears in the command history.
The second rule is what makes your tool part of your script. Every click on your item appears in eegh as a line of code, as you learned in the command history lesson.
In this lesson we write a plugin called chanvar: it plots the standard deviation of each channel and colors in red the channels far from the rest. A quick check tool, before or after the bad channels lesson.
How do I do it in EEGLAB?
7 steps in the menus
First: create the plugin folder
- Type in the command window:
pluginDir = fullfile(fileparts(which('eeglab')), 'plugins', 'chanvar1.0');
mkdir(pluginDir); cd(pluginDir);The folder name carries the version: EEGLAB reads chanvar1.0 as a plugin named chanvar, version 1.0.
Second: write the pop_ function
- Type
edit pop_chanvar.m, paste this code, and save:
function com = pop_chanvar(EEG, zThresh)
% POP_CHANVAR - Plot the standard deviation of each channel and
% highlight channels far from the others.
%
% Usage:
% >> pop_chanvar(EEG); % ask for the threshold in a window
% >> pop_chanvar(EEG, zThresh); % plot directly
%
% Inputs:
% EEG - EEGLAB dataset
% zThresh - robust z-score above which a channel is highlighted
% (default in the window: 3)
%
% Output:
% com - command string for the EEGLAB history
com = '';
if nargin < 1
help pop_chanvar;
return;
end
% Missing threshold: ask for it
if nargin < 2
res = inputgui('geometry', {[2 1]}, ...
'uilist', { {'style', 'text', 'string', 'Robust z-score threshold'}, ...
{'style', 'edit', 'string', '3'} }, ...
'title', 'Channel variance -- pop_chanvar()');
if isempty(res), return; end % user pressed Cancel
zThresh = str2double(res{1});
end
% Standard deviation of each channel over all samples (and epochs)
data = double(reshape(EEG.data, EEG.nbchan, []));
sd = std(data, 0, 2);
% Robust z-score based on the median absolute deviation
medSd = median(sd);
z = (sd - medSd) / (1.4826 * median(abs(sd - medSd)));
flagged = find(z > zThresh);
% Plot
figure('Name', 'Channel variance');
bar(sd, 'FaceColor', [0.6 0.6 0.6]); hold on;
if ~isempty(flagged)
bar(flagged, sd(flagged), 'FaceColor', [0.85 0.2 0.2]);
end
set(gca, 'XTick', 1:EEG.nbchan, 'XTickLabel', {EEG.chanlocs.labels});
xtickangle(90);
ylabel('Standard deviation (\muV)');
title(sprintf('%s: %d channel(s) above z = %g', EEG.setname, ...
numel(flagged), zThresh), 'Interpreter', 'none');
if ~isempty(flagged)
fprintf('Channels above threshold: %s\n', ...
strjoin({EEG.chanlocs(flagged).labels}, ' '));
end
% Command that reproduces this call
com = sprintf('pop_chanvar(EEG, %g);', zThresh);Third: write the eegplugin_ file
- Type
edit eegplugin_chanvar.m, paste this code, and save:
function vers = eegplugin_chanvar(fig, trystrs, catchstrs)
% EEGPLUGIN_CHANVAR - Add "Channel variance" to the EEGLAB Plot menu.
vers = 'chanvar1.0';
% Find the Plot menu of the main EEGLAB window
plotmenu = findobj(fig, 'tag', 'plot');
% Check the data, run the function, then add its command to the history
cmd = [trystrs.check_data 'LASTCOM = pop_chanvar(EEG);' catchstrs.add_to_hist];
uimenu(plotmenu, 'Label', 'Channel variance', 'CallBack', cmd, 'Separator', 'on');Fourth: try the plugin
- Close the EEGLAB window and type
eeglabagain, because EEGLAB looks for plugins only at startup. The command window prints a line containingadding "chanvar" v1.0. - Load D1 as in “Starting point”, but from the GUIThe windows and menus used with the mouse instead of typed commands.: File then Load existing dataset.
- Open Plot then Channel variance. Your window appears with
3in it. Click Ok. The plot appears. - Type
eegh. The last line ispop_chanvar(EEG, 3);. Your tool is now part of the command history like any official tool.
How do I do it in EEGLAB?
First: create the plugin folder
- Type in the command window:
pluginDir = fullfile(fileparts(which('eeglab')), 'plugins', 'chanvar1.0');
mkdir(pluginDir); cd(pluginDir);The folder name carries the version: EEGLAB reads chanvar1.0 as a plugin named chanvar, version 1.0.
Second: write the pop_ function
- Type
edit pop_chanvar.m, paste this code, and save:
function com = pop_chanvar(EEG, zThresh)
% POP_CHANVAR - Plot the standard deviation of each channel and
% highlight channels far from the others.
%
% Usage:
% >> pop_chanvar(EEG); % ask for the threshold in a window
% >> pop_chanvar(EEG, zThresh); % plot directly
%
% Inputs:
% EEG - EEGLAB dataset
% zThresh - robust z-score above which a channel is highlighted
% (default in the window: 3)
%
% Output:
% com - command string for the EEGLAB history
com = '';
if nargin < 1
help pop_chanvar;
return;
end
% Missing threshold: ask for it
if nargin < 2
res = inputgui('geometry', {[2 1]}, ...
'uilist', { {'style', 'text', 'string', 'Robust z-score threshold'}, ...
{'style', 'edit', 'string', '3'} }, ...
'title', 'Channel variance -- pop_chanvar()');
if isempty(res), return; end % user pressed Cancel
zThresh = str2double(res{1});
end
% Standard deviation of each channel over all samples (and epochs)
data = double(reshape(EEG.data, EEG.nbchan, []));
sd = std(data, 0, 2);
% Robust z-score based on the median absolute deviation
medSd = median(sd);
z = (sd - medSd) / (1.4826 * median(abs(sd - medSd)));
flagged = find(z > zThresh);
% Plot
figure('Name', 'Channel variance');
bar(sd, 'FaceColor', [0.6 0.6 0.6]); hold on;
if ~isempty(flagged)
bar(flagged, sd(flagged), 'FaceColor', [0.85 0.2 0.2]);
end
set(gca, 'XTick', 1:EEG.nbchan, 'XTickLabel', {EEG.chanlocs.labels});
xtickangle(90);
ylabel('Standard deviation (\muV)');
title(sprintf('%s: %d channel(s) above z = %g', EEG.setname, ...
numel(flagged), zThresh), 'Interpreter', 'none');
if ~isempty(flagged)
fprintf('Channels above threshold: %s\n', ...
strjoin({EEG.chanlocs(flagged).labels}, ' '));
end
% Command that reproduces this call
com = sprintf('pop_chanvar(EEG, %g);', zThresh);Third: write the eegplugin_ file
- Type
edit eegplugin_chanvar.m, paste this code, and save:
function vers = eegplugin_chanvar(fig, trystrs, catchstrs)
% EEGPLUGIN_CHANVAR - Add "Channel variance" to the EEGLAB Plot menu.
vers = 'chanvar1.0';
% Find the Plot menu of the main EEGLAB window
plotmenu = findobj(fig, 'tag', 'plot');
% Check the data, run the function, then add its command to the history
cmd = [trystrs.check_data 'LASTCOM = pop_chanvar(EEG);' catchstrs.add_to_hist];
uimenu(plotmenu, 'Label', 'Channel variance', 'CallBack', cmd, 'Separator', 'on');Fourth: try the plugin
- Close the EEGLAB window and type
eeglabagain, because EEGLAB looks for plugins only at startup. The command window prints a line containingadding "chanvar" v1.0. - Load D1 as in “Starting point”, but from the GUI: File then Load existing dataset.
- Open Plot then Channel variance. Your window appears with
3in it. Click Ok. The plot appears. - Type
eegh. The last line ispop_chanvar(EEG, 3);. Your tool is now part of the command history like any official tool.
How do I write it as code?
The same steps in 10 lines of MATLAB
The pop_ function also works from a script, without the menu. This test checks the basic rule: the string the function returns reproduces the same result.
eeglab nogui
eeglabRoot = fileparts(which('eeglab'));
EEG = pop_loadset('eeglab_data.set', fullfile(eeglabRoot, 'sample_data'));
% The plugin folder is on the path even without the GUI
assert(exist('pop_chanvar', 'file') == 2, 'pop_chanvar not found: check the plugins folder');
% Call with all arguments: no window, direct plot
com = pop_chanvar(EEG, 3);
disp(com); % pop_chanvar(EEG, 3);
% Running the returned command must reproduce the same figure
eval(com);How do I write it as code?
The pop_ function also works from a script, without the menu. This test checks the basic rule: the string the function returns reproduces the same result.
eeglab nogui
eeglabRoot = fileparts(which('eeglab'));
EEG = pop_loadset('eeglab_data.set', fullfile(eeglabRoot, 'sample_data'));
% The plugin folder is on the path even without the GUI
assert(exist('pop_chanvar', 'file') == 2, 'pop_chanvar not found: check the plugins folder');
% Call with all arguments: no window, direct plot
com = pop_chanvar(EEG, 3);
disp(com); % pop_chanvar(EEG, 3);
% Running the returned command must reproduce the same figure
eval(com);Which value should I choose?
Suitable values for your analysis goal
| Setting | Value in this lesson | Why |
|---|---|---|
| Folder location | plugins inside the EEGLAB folder | This is where EEGLAB looks at every startup for folders holding a file that starts with eegplugin (eeglab.m). |
| Folder name | chanvar1.0 | Digits at the end of the name are read as a version. If it differs from the version eegplugin_ returns, EEGLAB prints a warning. |
| Data check | trystrs.check_data | Makes sure a datasetOne EEG data file loaded in EEGLAB together with its information. is loaded before running. For functions that need epochA short time segment cut out around an event. use trystrs.check_epoch, and for ones that need ICAA method that separates the recording into independent sources, some brain and some artifact. trystrs.check_ica. |
| History | catchstrs.add_to_hist | Adds LASTCOM to the history and stores the dataset in ALLEEG. If the command starts with EEG =, EEGLAB creates a new dataset automatically. |
| Highlight thresholdA value beyond which the signal is treated as a problem. | z = 3 | A common starting value for outliers. The tool is for visual checking, not for removing channels automatically. |
Which value should I choose?
| Setting | Value in this lesson | Why |
|---|---|---|
| Folder location | plugins inside the EEGLAB folder | This is where EEGLAB looks at every startup for folders holding a file that starts with eegplugin (eeglab.m). |
| Folder name | chanvar1.0 | Digits at the end of the name are read as a version. If it differs from the version eegplugin_ returns, EEGLAB prints a warning. |
| Data check | trystrs.check_data | Makes sure a dataset is loaded before running. For functions that need epochs use trystrs.check_epoch, and for ones that need ICA trystrs.check_ica. |
| History | catchstrs.add_to_hist | Adds LASTCOM to the history and stores the dataset in ALLEEG. If the command starts with EEG =, EEGLAB creates a new dataset automatically. |
| Highlight threshold | z = 3 | A common starting value for outliers. The tool is for visual checking, not for removing channels automatically. |
How do I check it worked?
4 quick checks
- When you start
eeglab, the command window prints a line containingadding "chanvar" v1.0. - The Plot then Channel variance item exists and opens a window with the threshold box.
- After clicking Ok, a bar plot appears with one bar per channel (32 in D1).
eeghshowspop_chanvar(EEG, 3);, and the script above runs without error.
How do I check it worked?
- When you start
eeglab, the command window prints a line containingadding "chanvar" v1.0. - The Plot then Channel variance item exists and opens a window with the threshold box.
- After clicking Ok, a bar plot appears with one bar per channel (32 in D1).
eeghshowspop_chanvar(EEG, 3);, and the script above runs without error.
What mistakes do people make?
5 mistakes and how to avoid them
- Putting the two files outside the
pluginsfolder. EEGLAB does not look anywhere else, so the item does not appear. - A
pop_function that returns nothing. Withoutcom, nothing is recorded ineegh, so your click cannot be repeated in your script. - A command string that does not reproduce the result. If the user types 2.5 in the window, the string must be
pop_chanvar(EEG, 2.5);, not the default value. That is whycomis built from the values actually used. - Expecting the item to appear with
eeglab nogui. In this mode EEGLAB adds the plugin folder to the path but does not runeegplugin_, so there are no menus. This is normal. - Testing the plugin from the menu only. EEGLAB catches errors coming from menus. To trace an error, type
dbstop if errorand call the function from the command window (advice from the official documentation).
What mistakes do people make?
- Putting the two files outside the
pluginsfolder. EEGLAB does not look anywhere else, so the item does not appear. - A
pop_function that returns nothing. Withoutcom, nothing is recorded ineegh, so your click cannot be repeated in your script. - A command string that does not reproduce the result. If the user types 2.5 in the window, the string must be
pop_chanvar(EEG, 2.5);, not the default value. That is whycomis built from the values actually used. - Expecting the item to appear with
eeglab nogui. In this mode EEGLAB adds the plugin folder to the path but does not runeegplugin_, so there are no menus. This is normal. - Testing the plugin from the menu only. EEGLAB catches errors coming from menus. To trace an error, type
dbstop if errorand call the function from the command window (advice from the official documentation).
Your browser doesn't allow saving progress. All lessons are open, but completion marks won't be saved.
Done. Your progress is saved in this browser.UndoWhere can I read more?
2 sources, official docs first
- EEGLAB TutorialDocumentationContribute > Create an EEGLAB plugin
- EEGLAB TutorialDocumentationConcepts guide > EEGLAB functions
Written for EEGLAB 2026.1 · 2026-10-28
Going further
- EEGLAB Tutorial: https://eeglab.org/tutorials/contribute/design_plugin.html (Contribute > Create an EEGLAB plugin)
- EEGLAB Tutorial: https://eeglab.org/tutorials/ConceptsGuide/EEGLAB_functions.html (Concepts guide > EEGLAB functions)
Need help?
Ask, share feedback, or report an error
- Report a scientific error · Needs a GitHub account. Don't have one? Use "Share your thoughts".