Developers Docs ↗ Developers
Developers

Extension & developer docs

Build your own generators, tools, panels, effects, export formats and more on the public extension API. It is additive and versioned; feature-detect newer points and your extension keeps working across editor versions.

JavaScript APIPHP hooksThe bridgeClient-side

Extension API Developers #

Build your own generators, tools, panels and more.

The editor exposes a public extension API. An extension can add design generators, asset-library sections, starter-template packs, local AI tools (with access to the self-hosted transformer runtime), pixel effects, filter presets, menu items, panels, properties-panel sections, canvas tools, export formats and stock providers, and react to save events, all without touching the plugin’s code.

There are two ways to ship an extension:

  1. Extension package (recommended): a ZIP with a manifest.json and a JavaScript entry file, installed inside the editor under Extensions, then Manage Extensions. It never appears in the WordPress plugin list, loads only on the editor page, and is pure client-side (the archive may not contain PHP).
  2. WordPress plugin: for extensions that also need server-side hooks (stock providers, save events, bootstrap data).

Extension packages Developers #

A package is a ZIP whose top level contains a manifest.json:

{
  "name": "Paper Doodles",
  "slug": "wpie-paper-doodles",
  "version": "1.0.0",
  "author": "Jane Doe",
  "description": "Doodle shapes for the asset library.",
  "main": "extension.js",
  "style": "style.css",
  "requiresApi": "2.0.0",
  "provides": [ "librarySection" ]
}
  • name and main are required; slug defaults to a sanitized name; style is enqueued alongside the script.
  • Allowed file types: JS, JSON, CSS, source maps, images, fonts, text and 3D assets. PHP is rejected, so packages can never run server-side code.
  • Installing needs manage_options; packages install to uploads/wpie-extensions/<slug>/, and re-installing a slug replaces it (that is the update path).

The entry script registers the same way whether it ships in a package or a plugin:

function register( api ) { /* api.register…() calls */ }
if ( window.WPIE?.api ) {
  register( window.WPIE.api );
} else {
  wp.hooks.addAction( 'wpie.ready', 'my-extension', register );
}

From a WordPress plugin, enqueue on the wpie_editor_assets action (it fires only on the editor page) and depend on wpie-editor and wp-hooks.

Localizing a pack

The editor text domain only covers core strings; packs ship their own translations. Read the editor user’s locale from window.WPIE.locale (falling back to <html lang>) and pick a bundled dictionary.

JavaScript API Developers #

window.WPIE.api is the registration surface. api.version is the API semver; feature-detect newer points with if ( api.registerAiTool ) { … }. All ids must be namespaced like Gutenberg blocks: my-plugin/my-thing. Every callback receives { editor, extras } (see The bridge). One example per point:

registerGenerator( generator )

An entry in the Extensions menu that inserts layers. If the layer carries generator = { id, params }, it gets an Edit re-entry in the context menus.

api.registerGenerator( {
  id: 'my-plugin/confetti',
  label: 'Confetti Generator',
  run( { editor, extras } ) {
    const { makeShape } = window.WPIE.bridge.documents;
    const layer = makeShape( { name: 'Confetti', x: 40, y: 40, w: 300, h: 300, pathD: '…' } );
    layer.generator = { id: 'my-plugin/confetti', params: { density: 5 } };
    editor.dispatch( { type: 'ADD_LAYER', layer } );
    editor.commit( 'Insert Confetti' );
  },
  edit( { editor, extras, layer } ) { /* reopen your UI for `layer` */ },
} );

registerEffect( effect )

A pure pixel kernel (no DOM, no async). It appears in the Filter menu, the Adjust panel and as a Smart Filter; preview and export share the code path.

api.registerEffect( {
  id: 'my-plugin/scanlines',
  label: 'Scanlines',
  params: {
    gap:    { min: 2, max: 12, default: 3 },       // slider
    strong: { type: 'bool', default: false },      // checkbox
    tint:   { type: 'color', default: '#001122' }, // color
  },
  apply( img, params ) {
    // img = { data: Uint8ClampedArray (RGBA), width, height }. Mutate in place.
    return img;
  },
} );

registerFilterPreset( preset )

A one-click CSS filter preset, like the built-in filter list. Use widely-supported filter functions; the editor emulates them pixel-exactly where ctx.filter is missing.

api.registerFilterPreset( {
  id: 'my-plugin/hero',
  label: 'Hero',
  css: 'saturate(1.4) contrast(1.15)',
} );

registerMenuItem( menuId, item )

menuId is one of file, edit, image, layer, select, filter, view, tools, extensions, help (plus the automation submenus). Items appear behind a divider at the end of the menu.

api.registerMenuItem( 'filter', {
  label: 'My Action…',
  run( { editor, extras } ) {
    extras.toasts.success( 'Layers: ' + editor.state.layers.length );
  },
  when: ( { editor } ) => !! editor.state.activeId, // optional enable-gate
} );

registerPanel( panel )

A tab in the right dock. render gets a plain DOM element (mount any framework yourself) and re-runs when layers or the active layer change; return a cleanup function.

api.registerPanel( {
  id: 'my-plugin/inspector',
  title: 'Inspector',
  render( el, { editor, extras } ) {
    el.innerHTML = '<div style="padding:12px">…</div>';
    return () => {/* cleanup */};
  },
} );

registerPanelSection( section )

A section in the Properties panel, gated per layer via when. Read live values from editor.state inside handlers, not from captured variables.

api.registerPanelSection( {
  id: 'my-plugin/confetti-props',
  title: 'Confetti',
  when: ( layer ) => layer.generator?.id === 'my-plugin/confetti',
  render( el, { editor, extras, layer } ) {
    el.innerHTML = '<label>…controls…</label>';
    return () => {/* cleanup */};
  },
} );

registerTool( tool )

A button in the left tool rail whose pointer handlers dispatch like built-in tools. Handlers receive the tool context tc, the DOM event and the point in document coordinates.

api.registerTool( {
  id: 'my-plugin/stamp-circle',
  label: 'Circle Stamp',
  icon: '<svg viewBox="0 0 24 24" width="18">…</svg>',
  handlers: {
    onDown( tc, event, p ) { /* start */ },
    onMove( tc, event, p ) { /* drag */ },
    onUp( tc, event, p ) { tc.editor.commit( 'Circle Stamp' ); },
  },
} );

registerLibrarySection( section )

A chip in the Asset Library tray, the home for asset packs. items is a static array or a function of the query (may return a Promise, so remote sources work).

api.registerLibrarySection( {
  id: 'my-plugin/doodles',
  label: 'Doodles',
  items: [
    {
      name: 'Heart',
      preview: 'data:image/svg+xml;utf8,…', // any image URL
      asset: { kind: 'user-element', descriptor: { name: 'Heart', pathD: 'M …' } },
    },
  ],
} );

registerTemplatePack( pack )

Installable starter templates that join the built-in ones. templates is an array or a function returning one (a Promise works, so large packs fetch a JSON asset lazily).

// The script's own URL makes fetches package-relative.
const baseUrl = ( document.currentScript?.src || '' ).replace( /[^/]*$/, '' );

api.registerTemplatePack( {
  id: 'my-plugin/holiday',
  label: 'Holiday Templates',
  templates: () =>
    window.fetch( baseUrl + 'templates.json' ).then( ( r ) => r.json() ),
} );

registerAiTool( tool )

A button in AI Studio, Local tools. It runs inside the panel busy/error harness; use the bridge for self-hosted transformer models, so inference never leaves the browser.

api.registerAiTool( {
  id: 'my-plugin/name-layers',
  label: 'Name Layers',
  title: 'Names every image layer with a local vision transformer.',
  async run( { editor, extras, layer } ) {
    const TF = await window.WPIE.bridge.ml.loadTransformers();
    const pipe = await TF.pipeline( 'image-to-text', 'Xenova/vit-gpt2-image-captioning' );
    // … caption layers, dispatch UPDATE_LAYER patches, commit.
  },
} );

registerExportFormat( format )

Adds a button to the Export dialog. render() gives you the flattened canvas from the real pipeline; encode must return a Blob.

api.registerExportFormat( {
  id: 'my-plugin/tiff',
  label: 'TIFF',
  ext: 'tiff',
  async encode( { doc, layers, render, scale, quality } ) {
    const canvas = await render();
    return myTiffEncoder( canvas );
  },
} );

Effects are stored by id in the project JSON; if a project is opened without your extension the effect is skipped gracefully and preserved in the file, so no data is lost.

PHP hooks Developers #

For WordPress-plugin extensions that need server-side behavior:

HookTypePurpose
wpie_editor_assetsaction ( $attachment_id )Enqueue editor-page scripts and styles.
wpie_bootstrap_datafilter ( $data, $attachment_id, $is_new )Add values to window.WPIE.
wpie_stock_providersfilter ( $providers )Register stock-image sources.
wpie_after_saveaction ( $attachment_id, $file, $context )React to saves (save = overwrite, saveAs = new attachment).
wpie_settings_tabsfilter ( $tabs )Add a tab to the settings page.
wpie_settings_extra_tabsaction ( $active_tab )Render your added settings tab.
wpie_ai_modelsfilter ( $models, $provider )Adjust the AI model list per provider.

The bridge Developers #

window.WPIE.bridge hands extensions the editor’s own building blocks, so they never bundle their own copies. It is additive: members are never renamed or removed. The full surface:

Documents, rendering & assets

  • documents: layer factories and (de)serialization, makeShape, makeText, makeImage, makeGroup, createBlankDoc, loadImage, hydrateLayers, serializeLayers.
  • raster: renderToCanvas, renderToBlob, sharedImageCache.
  • svg: exportSvg (keeps dynamic-binding markers), importSvg.
  • fonts: ensureFontsForLayers. icons: the Tabler icon set. aspect: nearestAspect.

Data, templates & export

  • dynamicContent: resolveBindings, prepareGeneratorLayers (re-render generator layers per post), collectMetaKeys, plus the token helpers expandTokens, hasTokens and bindingGroups for {{token}} fields and pickers.
  • templatesLib: loadTemplate, invalidateTemplate, renderResolved (a template plus post context to a finished canvas).
  • csv: parseCsv. exportPresets: listExportPresets. watermark: applyWatermark. providers: PROVIDER_LABELS, hasTextProvider.
  • macros: listMacros, runMacro, createHeadlessEditor (run edits off-screen).

AI & local models

  • ml: loadTransformers (the self-hosted transformers.js runtime), mlPipeline with WebGPU auto-fallback, preferredDevice, isModelInstalled, modelsBaseUrl. Inference never leaves the browser.

Editor UI components

  • components: mount the editor’s own controls into your extension, framework-free. mountColorButton( node, { color, onChange, title } ) renders the native color swatch and picker (returns { set, unmount }); mountVarButton inserts {{token}} variables; mountStyleButton is the AI style-preset picker; HelpLink is the standard help link.

Server-backed helpers (bridge.api)

  • posts (post and product catalog / search), templates, automation, and ai (image and text generation).
  • geo: a capability-gated, server-side OpenStreetMap proxy for map extensions (geo.search, geo.map), cached on your server.
  • Media helpers: saveToLibrary, saveAsNew, buildFormData, getMediaMeta, updateMediaAlt, proxyImageUrl.
The callback context

Every registration callback receives { editor, extras }. editor is the live editor: state (doc, layers, selection, activeId), dispatch( action ), commit( label ) (a history snapshot), undo() and redo(). extras carries toasts and around forty dialog openers, so an extension can launch core features directly, for example extras.openColorSchemer(), extras.openChart(), extras.openQr(), extras.openStock(), extras.openGenerate(), extras.openMockup(), extras.openExport() and many more.

Rules of the road Developers #

  • Namespace everything (my-plugin/thing); unprefixed ids are rejected.
  • Effects must be pure pixel functions: no DOM, no async, no state. They run in the live preview on every frame; keep them linear over pixels.
  • Detect API capabilities with api.version or !! api.registerGenerator instead of failing hard on older editor versions.
  • Projects that reference a missing extension effect still open; the effect is skipped at render and preserved in the file.
  • The editor is GPL; extensions interacting this deeply are derivative works, license accordingly.