PixelShop

Plugin Developer Reference

Drop any .js file into the /plugins folder next to the executable. It will be picked up automatically on launch.

Plugin Structure

Your file must export an object with the following fields:

id - string, required, unique identifier such as "my-cool-tool"
name - string, required, display name shown in the panel header and Plugins menu
width - number, optional, default panel width in px, default 360
height - number, optional, default panel height in px, default 420, including the header
fitWidth / fitHeight - number, optional, size used when the user clicks the fit button
mount(ctx) - function, required, called once when the panel is first opened

mount() receives a context object:

ctx.el - the DOM element you should build your UI inside
ctx.api - helper methods and app hooks

If mount() returns a function, it will be used as cleanup when the plugin is destroyed.

ctx.api - Plugin API

These are the safe helper methods passed to your plugin through ctx.api:

api.toast(msg) - show a toast notification
api.getTool() - returns current tool name
api.setTool(name) - switch to a tool by name
api.requestRender() - redraw the canvas after changing pixels
api.canvas() - returns the main pixel canvas element
api.overlays() - returns { cursorCanvas, gridCanvas, refCanvas }

App API - Colors

External plugins are loaded as CommonJS modules, so use ctx.api instead of relying on free globals like currentColor.

api.getFG() - get primary foreground color hex string
api.getBG() - get secondary background color hex string
api.setFG(hex) - set foreground color and update UI
api.setBG(hex) - set background color and update UI
api.TRANSPARENT - the string "transparent" for erased pixels, if exposed

Globals - Canvas

tilesWidth, tilesHeight - canvas size in tiles
tileSize - pixels per tile
pixelSize - zoom level in screen px per canvas px
zoomLevel - current zoom multiplier
canvasOffset - pan offset object { x, y }
canvasData - 2D array [y][x] of hex strings or null

Globals - Layers

layers - array of layer objects
currentLayerId - active layer id

Each layer object has { id, name, visible, opacity, blendMode, data, offsetX, offsetY, clippingMask, styles, tlLocked }.

createLayer(name) - create a new layer
addLayer() - add a layer and update UI
deleteLayer() - delete the active layer
selectLayer(id) - switch active layer
mergeDown(id) - merge layer with the one below
toggleLayerVisibility(id) - show or hide a layer
renderLayersPanel() - refresh the layers panel UI

Globals - Drawing

drawPixel(x, y, color) - draw one pixel on the active layer
floodFill(x, y, color) - flood fill from a point
drawCanvas() - full canvas redraw across layers

Globals - Tools

currentTool - current tool name string
selectTool(name) - switch tool and update UI
brushSize - pencil brush size
eraserSize - eraser size

Tool names include pencil, eraser, fill, eyedropper, move, marquee, shape, line, gradient, dither, zoom, and pan.

App API - Palette

Use these helpers to read, switch, render, or create palettes from an external plugin:

api.getPalettes() - returns { key: { name, colors[] } }
api.getCurrentPalette() - returns the active palette key
api.changePalette(key) - switch palette and re-render
api.renderPalette() - refresh palette display
api.updatePaletteDeleteBtn() - update delete button state
api.savePalette(name, colors) - create a new palette, store it, and switch to it

System palettes that cannot be deleted: canvas, gb, gbc, gba, snes.

const colors = ["#ff0000", "#00ff00", "#0000ff"];
ctx.api.savePalette("My Ramp", colors);

Globals - History

saveHistory(label) - push current state to the undo stack
undo() - undo last action
redo() - redo last undone action
history - array of history state objects
historyStep - current position in the history stack

Globals - Selection

selection - current selection object or null
marqueeMode - selection mode such as "rect"

Globals - Dialogs

Use these instead of alert, confirm, or prompt so the UI matches the app theme.

themedAlert(msg, title?) - themed alert dialog, returns a promise
themedConfirm(msg, title?) - themed confirm dialog, resolves to true or false
themedPrompt(msg, default?, title?) - themed input prompt, resolves to a string or null

Globals - View

resetView() - reset zoom and pan
fitToScreen() - fit the canvas to the viewport
setZoomAbsolute(z) - set zoom to a specific level
showGrid - boolean for grid visibility

Globals - Import and Export

importPng() - open PNG import dialog
importPalette() - open palette import dialog
exportAnimatedGif() - open GIF export dialog

CSS Variables

These custom properties are defined on :root and update with the theme.

--bg-main: #535353        Main background
--bg-dark: #333333        Darker bg areas
--bg-darker: #282828      Darkest bg
--panel-bg: #3d3d3d       Panel background
--panel-header: #2e2e2e   Panel header bar
--panel-border: #1a1a1a   Borders between panels
--accent: #1473e6         Accent / active highlights
--accent-hover: #0d66d0   Accent on hover
--text: #d4d4d4           Primary text
--text-dim: #a0a0a0       Secondary / dimmed text
--hover: #505050          Hover state bg
--active: #5a5a5a         Active or pressed bg
--button-bg: #4a4a4a      Button backgrounds
--input-bg: #2a2a2a       Input field backgrounds
--border-light: #505050   Lighter borders on inputs

These values are shown for the dark theme and change automatically when the theme changes.

Native CSS Classes

Use these classes to match the app look and theme behavior.

.option-btn - standard button
.seg-btn - segmented control button
.segmented - wrapper for grouped segment buttons
.seg-btn.active - active segment state
.size-slider - styled range input
.size-number - small number input
.tool-select - styled select dropdown
.option-group - flex row with gap for controls
.option-label - dimmed label text
.checkbox-wrapper - checkbox and label row
.checkbox-label - checkbox label text
.tl-btn - compact timeline button

UI Recipes

Button

const btn = document.createElement("button");
btn.className = "option-btn";
btn.textContent = "Do Thing";

Slider and number input

const row = document.createElement("div");
row.className = "option-group";

const label = document.createElement("span");
label.className = "option-label";
label.textContent = "Size";

const slider = document.createElement("input");
slider.type = "range";
slider.className = "size-slider";
slider.min = "1";
slider.max = "32";

const num = document.createElement("input");
num.type = "number";
num.className = "size-number";

row.append(label, slider, num);

Dropdown select

const sel = document.createElement("select");
sel.className = "tool-select";
["Option A", "Option B"].forEach(t => {
  const o = document.createElement("option");
  o.value = t;
  o.textContent = t;
  sel.appendChild(o);
});

Segmented control

const seg = document.createElement("div");
seg.className = "segmented";
["1x", "2x", "4x"].forEach((label, i) => {
  const btn = document.createElement("button");
  btn.className = "seg-btn" + (i === 0 ? " active" : "");
  btn.textContent = label;
  btn.onclick = () => {
    seg.querySelectorAll(".seg-btn").forEach(b => b.classList.remove("active"));
    btn.classList.add("active");
  };
  seg.appendChild(btn);
});

Checkbox

const wrap = document.createElement("label");
wrap.className = "checkbox-wrapper";
const cb = document.createElement("input");
cb.type = "checkbox";
const lbl = document.createElement("span");
lbl.className = "checkbox-label";
lbl.textContent = "Enable thing";
wrap.append(cb, lbl);

Minimal Plugin Template

module.exports = {
  id: "my-plugin",
  name: "My Plugin",
  width: 300,
  height: 200,

  mount(ctx) {
    const btn = document.createElement("button");
    btn.className = "option-btn";
    btn.textContent = "Hello!";
    btn.onclick = () => ctx.api.toast("It works!");
    ctx.el.appendChild(btn);

    return () => {};
  }
};

Notes