Create an icon pack for TinyMCE

Overview

This guide provides comprehensive coverage of creating, building, and deploying custom icon packs, including solutions to common issues.

Prerequisites

This guide assumes:

  • Familiarity with the command line and running commands

  • Node.js and NPM are already installed

  • Optional: git is already installed

How Icons Work in TinyMCE

A TinyMCE icon pack is a .js file containing strings of SVG’s. An icon pack can be used: to include one or more custom icons; or to replace some or all of the default TinyMCE icons.

An icon pack only requires the custom icons to be included; the default TinyMCE icons are used as a fallback for icons missing from the custom icon pack.

Icon Pack Structure and Loading

Understanding Icon Pack Loading:

TinyMCE loads icon packs from the path TINYMCE_BASE/icons/${iconPackName}/icons.js, where:

  • TINYMCE_BASE is the TinyMCE root directory (the directory containing tinymce.min.js).

  • ${iconPackName} is the name of the icon pack.

Example Directory Structure:

js/tinymce/
โ”œโ”€โ”€ tinymce.min.js          โ† TINYMCE_BASE marker file
โ”œโ”€โ”€ icons/
โ”‚   โ”œโ”€โ”€ default/
โ”‚   โ”‚   โ””โ”€โ”€ icons.js        โ† Default TinyMCE icons
โ”‚   โ””โ”€โ”€ my_icon_pack/       โ† Your custom pack
โ”‚       โ””โ”€โ”€ icons.js        โ† Must be exactly this filename
โ””โ”€โ”€ ... (other TinyMCE files)

Icon Pack File Format

The generated icons.js file in the dist/icons/${iconPackName}/icons.js directory follows this exact format:

tinymce.IconManager.add('your-pack-name', {
  icons: {
    'bold': '<svg width="24" height="24">...</svg>', // this is automatically generated from the 'bold.svg' file in the 'src/svg' directory
    'italic': '<svg width="24" height="24">...</svg>',
    'custom-icon': '<svg width="24" height="24">...</svg>',
    // ... more icons
  }
});

Creating a TinyMCE Icon Pack

To create a custom icon pack:

Download and Setup the Icon Pack Template

To use the TinyMCE icon pack template project:

  1. Download the TinyMCE Oxide Icon Pack Template by either:

    • Downloading the .zip file from the Oxide Icon Pack Template GitHub page and extract the contents.

    • From a terminal or command prompt, use git to clone the GitHub repository:

      git clone https://github.com/tinymce/oxide-icon-pack-template.git
  2. Open a terminal or command prompt, navigate to the oxide-icon-pack-template directory.

  3. Install the project dependencies by executing:

    npm install
  4. When prompted, enter a name for the icon pack. The icon pack name should only contain:

    • Numbers

    • Letters

    • Hyphens (-)

    • Underscores (_)

  5. Verify that the iconPackName field has been added to your package.json file:

    {
      "iconPackName": "my_icon_pack",
    }

The icon pack name will be used with the icons option to apply the icons in TinyMCE.

The iconPackName field in package.json is essential for the build process. This field:

  • Defines the name that will be used in the generated icons.js file

  • Must match the name you use in the icons option when initializing TinyMCE

  • Is used by the build system to create the proper directory structure

  • If missing or incorrect, the icon pack will not work properly

Example:

  • package.json contains: "iconPackName": "my_icon_pack"

  • TinyMCE config uses: icons: 'my_icon_pack'

  • Generated file: tinymce.IconManager.add('my_icon_pack', {…​})

Add the SVG Files

Each SVG files placed in /src/svg will be converted to an icon. The file names of the SVG files are used to set the icon identifier used by TinyMCE.

For example: bold.svg will have the identifier bold. Such as:

tinymce.init({
  selector: '#tiny_custom_button',  // change this value according to your HTML
  toolbar: 'myButton',
  icons: 'my_icon_pack',
  setup: (editor) => {
    editor.ui.registry.addButton('myButton', {
      icon: 'bold',    // the 'bold' icon created from 'bold.svg'
      onAction: (_) => {
        editor.insertContent('&nbsp;<strong>It\'s my icon button!</strong>&nbsp;');
      }
    });
  }
});

For a list of default icon identifiers, see: Available icons. If using a custom icon pack, the icon identifiers will be the file names of the SVG files.

  • TinyMCE does not resize the SVGs provided, relying on the size defined in the SVG. This allows icons of different sizes to be used in the editor. The Toolbar button sizes are independent of the icon sizes. To change button sizes, a custom skin is required.

  • SVG Requirements: Input SVGs must be shapes, not strokes. SVG files containing strokes will not render correctly. If the input files contain strokes, use a graphics program to convert the strokes into shapes.

SVG File Organization

Organize your SVG files in the src/svg/ directory:

src/svg/
โ”œโ”€โ”€ bold.svg           โ† Overrides default bold icon
โ”œโ”€โ”€ italic.svg         โ† Overrides default italic icon
โ”œโ”€โ”€ underline.svg      โ† Overrides default underline icon
โ”œโ”€โ”€ my-custom-icon.svg โ† Creates new 'my-custom-icon' identifier
โ”œโ”€โ”€ company-logo.svg   โ† Creates new 'company-logo' identifier
โ””โ”€โ”€ save-action.svg    โ† Creates new 'save-action' identifier

Icon Identifier Rules:

  • Filename becomes the identifier: bold.svg โ†’ 'bold'

  • Hyphens are preserved: my-custom-icon.svg โ†’ 'my-custom-icon'

Build the Icon Pack

To build the icon pack using Gulp:

  1. Open a terminal or command prompt and navigate to the root directory of the icon pack (such as: oxide-icon-pack-template/).

  2. Build the icon pack by executing the npx gulp command:

    npx gulp

    A dist/ directory containing the icon pack will be automatically created.

    Example: dist/icons/my-icon-pack/icons.js automatically generated from the SVG files in the src/svg directory.
    tinymce.IconManager.add('my-icon-pack', {
      icons: {
        'audio': '<svg width="24" height="24"><path d="M10.8 19q.9 0 1.5-.7t.7-1.5V13h3v-2h-4v3.9l-.6-.3-.6-.1q-1 0-1.7.7t-.6 1.6q0 .9.7 1.5t1.6.7ZM6 22q-.8 0-1.4-.6T4 20V4q0-.8.6-1.4T6 2h8l6 6v12q0 .8-.6 1.4T18 22H6Zm7-13h5l-5-5v5Z"/></svg>',
        'bold': '<svg width="24" height="24"><path fill-rule="evenodd" d="M3.6 2.3a1.4 1.4 0 0 0-1.4 1.3v16.8c0 .7.7 1.4 1.4 1.4h16.8a1.4 1.4 0 0 0 1.4-1.4V3.6a1.4 1.4 0 0 0-1.4-1.3H3.6Zm6 4a1.4 1.4 0 0 0-1.3 1.3v9.3c0 .7.6 1.4 1.3 1.4H12v-.8.8a2.5 2.5 0 0 0 .2 0 4.6 4.6 0 0 0 1.6-.5c.5-.2 1-.5 1.3-1 .4-.5.7-1.2.7-2 0-.9-.3-1.6-.7-2a3.2 3.2 0 0 0-.8-.9 2.8 2.8 0 0 0 .4-.5c.4-.5.5-1.1.5-1.9s-.1-1.4-.5-1.9a3 3 0 0 0-1.1-1 3.9 3.9 0 0 0-1.6-.3H9.6Zm.1 6.5H12a3.2 3.2 0 0 1 1.2.2l.7.6c.2.2.4.6.4 1.1s-.2 1-.4 1.2a1.8 1.8 0 0 1-.7.6 3.2 3.2 0 0 1-1.1.2H9.7v-4Zm2.3 4Zm0-5.6H9.7V7.8H12l1 .2.5.5c.1.2.3.5.3 1s-.2.8-.3 1a1.4 1.4 0 0 1-.6.5 2.3 2.3 0 0 1-.9.2Z" clip-rule="evenodd"/></svg>',
        'italic': '<svg width="24" height="24"><path fill-rule="evenodd" d="M3.6 2.3a1.4 1.4 0 0 0-1.4 1.3v16.8c0 .7.7 1.4 1.4 1.4h16.8a1.4 1.4 0 0 0 1.4-1.4V3.6a1.4 1.4 0 0 0-1.4-1.3H3.6ZM16 6.8h-1.5L11 17.1h1a.8.8 0 0 1 0 1.6H8a.8.8 0 0 1 0-1.6h1.5L13 6.8h-1a.8.8 0 0 1 0-1.5h4a.8.8 0 0 1 0 1.5Z" clip-rule="evenodd"/></svg>',
      }
    });
  3. Using a web browser, open dist/html/icons.html to preview the icons.

Troubleshooting Information for Building Icon Packs

The SVG files are optimized during the build process with SVGO. The optimization can result in distorted graphics due to rounding errors. The graphics may be fixed by providing new SVGO options. To change the SVGO options used:

  1. Using a text editor, open gulpfile.js.

  2. Add the svgo option to the iconPackager function, such as:

    iconPackager({
      name: 'my-icon-pack',
      svgo: { floatPrecision: 3 } //Increase the rounding precision
    })

All user defined options, including SVGO options, will merge with the default options. For information on SVGO options, see: SVGO on GitHub.

Deploying an Icon Pack

An icon pack can be served either:

Deploy the Icon Pack with TinyMCE

On initialization, TinyMCE will try to load any icon pack specified by the icons option. The icons in the icon pack will be merged with TinyMCE’s default icons and icons in the icon pack will overwrite the default icons with the same identifier.

TinyMCE loads icon packs from the path TINYMCE_BASE/icons/${iconPackName}/icons.js; where:

  • TINYMCE_BASE is the TinyMCE root directory (the directory containing tinymce.min.js).

  • ${iconPackName} is the name of the icon pack.

Available icon packs

TinyMCE includes both community and premium icon packs:

  • Community: default (always required)

  • Premium: bootstrap, jam, material, small, thin (available with paid subscriptions)

For information on creating custom icon packs, see: Create an icon pack for TinyMCE.

Usage

To use a TinyMCE icon pack:

  1. If required, create a new icons directory in TINYMCE_BASE.

  2. Copy the icon pack into the icons directory. For example:

    $ cp -r  dist/icons/my_icon_pack  TINYMCE_BASE/icons/
  3. Add the icons option to tinymce.init.

For custom icon packs:

tinymce.init({
  selector: 'textarea',
  icons: 'my_icon_pack'  // TINYMCE_BASE/icons/my_icon_pack/icons.js
});

Deploy the Icon Pack and TinyMCE Separately

On initialization, TinyMCE will try to load any icon pack specified by the icons_url option. The icons in the icon pack will be merged with TinyMCE’s default icons and icons in the icon pack will overwrite the default icons with the same identifier.

icons_url is used to specify the location of an icon pack when TinyMCE and the icon pack are loaded from different locations. For example: When loading TinyMCE from Tiny Cloud, the icon pack can be loaded from a different web server.

Understanding icons_url and icons Together:

When using icons_url, you must also specify the icons option:

  • icons_url - Tells TinyMCE where to find the icon pack file.

  • icons - Tells TinyMCE which icon pack to use from the loaded file.

Example:

tinymce.init({
  selector: 'textarea',
  icons_url: 'dist/icons/my_icon_pack/icons.js', // Where to find it
  icons: 'my_icon_pack',  // Which pack to use
  // ... other options
});

The icons value must match the pack name defined in your package.json iconPackName field.

Usage

To use a TinyMCE icon pack from a separate location:

  1. Ensure the icon pack is available at the specified URL.

  2. Add the icons_url option to tinymce.init.

For custom icon packs:

tinymce.init({
  selector: 'textarea',  // change this value according to your HTML
  icons_url: 'dist/icons/my_icon_pack/icons.js', // Path to custom icon pack
  icons: 'my_icon_pack'  // Use custom icon pack
});

Common Development Scenarios

Local Development with Custom Icon Pack

When developing locally with a custom icon pack:

tinymce.init({
  selector: 'textarea',
  icons_url: 'dist/icons/my_icon_pack/icons.js', // Path to custom icon pack
  icons: 'my_icon_pack',  // Use custom icon pack
  toolbar: 'myButton',
  setup: (editor) => {
    editor.ui.registry.addButton('myButton', {
      icon: 'bold',    // Uses your custom bold icon
      onAction: (_) => {
        editor.insertContent('&nbsp;<strong>Custom icon clicked!</strong>&nbsp;');
      }
    });
  }
});

CDN Deployment

When deploying to a CDN or separate server:

tinymce.init({
  selector: 'textarea',
  icons_url: 'https://cdn.example.com/icons/my_icon_pack/icons.js', // CDN URL
  icons: 'my_icon_pack',  // Use custom icon pack
  // ... other options
});

Interactive Demo

See the custom icon pack in action with our interactive demo:

  • TinyMCE

  • HTML

  • JS

  • Edit on CodePen

Default TinyMCE Icons

Custom Icon Pack

Icon Credits: Custom icons in this demo are sourced from FreeSVGIcons.com - a collection of 250k+ open-source SVG icons.
<div style="display: flex; gap: 20px; width: 100%; flex-wrap: wrap;">
  <div style="flex: 1; min-width: 300px;">
    <h2>Default TinyMCE Icons</h2>
    <textarea id="default-editor" style="width: 100%;">
      <h2>Default Icons Editor</h2>
      <p>This editor uses the <strong>default TinyMCE icons</strong>.</p>
      <p>Notice the standard appearance of the toolbar buttons.</p>
      <ul>
        <li>Bold, italic, underline buttons</li>
        <li>Link and image buttons</li>
        <li>List and code buttons</li>
      </ul>
    </textarea>
  </div>
  <div style="flex: 1; min-width: 300px;">
    <h2>Custom Icon Pack</h2>
    <textarea id="custom-editor" style="width: 100%;">
      <h2>Custom Icons Editor</h2>
      <p>This editor uses a <strong>custom icon pack</strong> created with our icon pack system.</p>
      
      <h3>How It Works:</h3>
      <p><strong>Demo Setup:</strong> For this live demo, the custom icons are defined using <code>tinymce.IconManager.add()</code> directly in the JavaScript file.</p>
      
      <p><strong>Production Setup:</strong> In normal usage, you would:</p>
      <ul>
        <li>Build your icon pack using the template (see our guide)</li>
        <li>Place the generated <code>icons.js</code> file in <code>dist/icons/your-pack-name/</code></li>
        <li>Use <code>icons_url: 'path/to/icons.js'</code> to load it</li>
      </ul>
      
      <p>The editor is configured with <code>icons: 'my-icon-pack'</code> to use the custom icons. <strong>Try the custom buttons:</strong> Look for the custom bold, italic, and audio buttons in the toolbar above.</p>
    </textarea>
  </div>
</div>

<div style="margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px; font-size: 14px; color: #666;">
  <strong>Icon Credits:</strong> Custom icons in this demo are sourced from <a href="https://freesvgicons.com/" target="_blank" rel="noopener">FreeSVGIcons.com</a> - a collection of 250k+ open-source SVG icons.
</div>
// Load custom icon pack inline (for live demo compatibility)
tinymce.IconManager.add('my-icon-pack', {
  icons: {
      'audio': '<svg width="24" height="24"><path d="M10.8 19q.9 0 1.5-.7t.7-1.5V13h3v-2h-4v3.9l-.6-.3-.6-.1q-1 0-1.7.7t-.6 1.6q0 .9.7 1.5t1.6.7ZM6 22q-.8 0-1.4-.6T4 20V4q0-.8.6-1.4T6 2h8l6 6v12q0 .8-.6 1.4T18 22H6Zm7-13h5l-5-5v5Z"/></svg>',
      'bold': '<svg width="24" height="24"><path fill-rule="evenodd" d="M3.6 2.3a1.4 1.4 0 0 0-1.4 1.3v16.8c0 .7.7 1.4 1.4 1.4h16.8a1.4 1.4 0 0 0 1.4-1.4V3.6a1.4 1.4 0 0 0-1.4-1.3H3.6Zm6 4a1.4 1.4 0 0 0-1.3 1.3v9.3c0 .7.6 1.4 1.3 1.4H12v-.8.8a2.5 2.5 0 0 0 .2 0 4.6 4.6 0 0 0 1.6-.5c.5-.2 1-.5 1.3-1 .4-.5.7-1.2.7-2 0-.9-.3-1.6-.7-2a3.2 3.2 0 0 0-.8-.9 2.8 2.8 0 0 0 .4-.5c.4-.5.5-1.1.5-1.9s-.1-1.4-.5-1.9a3 3 0 0 0-1.1-1 3.9 3.9 0 0 0-1.6-.3H9.6Zm.1 6.5H12a3.2 3.2 0 0 1 1.2.2l.7.6c.2.2.4.6.4 1.1s-.2 1-.4 1.2a1.8 1.8 0 0 1-.7.6 3.2 3.2 0 0 1-1.1.2H9.7v-4Zm2.3 4Zm0-5.6H9.7V7.8H12l1 .2.5.5c.1.2.3.5.3 1s-.2.8-.3 1a1.4 1.4 0 0 1-.6.5 2.3 2.3 0 0 1-.9.2Z" clip-rule="evenodd"/></svg>',
      'italic': '<svg width="24" height="24"><path fill-rule="evenodd" d="M3.6 2.3a1.4 1.4 0 0 0-1.4 1.3v16.8c0 .7.7 1.4 1.4 1.4h16.8a1.4 1.4 0 0 0 1.4-1.4V3.6a1.4 1.4 0 0 0-1.4-1.3H3.6ZM16 6.8h-1.5L11 17.1h1a.8.8 0 0 1 0 1.6H8a.8.8 0 0 1 0-1.6h1.5L13 6.8h-1a.8.8 0 0 1 0-1.5h4a.8.8 0 0 1 0 1.5Z" clip-rule="evenodd"/></svg>',
  }
});

// Default Icons Editor
tinymce.init({
selector: '#default-editor',
icons: 'material', // use material icon pack
plugins: 'lists link image code',
toolbar: 'undo redo | bold italic underline | bullist numlist | link image code',
height: 500,
// license_key: 'gpl'
});

// Custom Icons Editor
tinymce.init({
selector: '#custom-editor',
icons: 'my-icon-pack',
plugins: 'lists link image code',
toolbar: 'undo redo | myButton1 myButton2 myButton3 | bullist numlist | link image code',
height: 500,
setup: (editor) => {
  editor.ui.registry.addButton('myButton1', {
    icon: 'bold',    // the 'bold' icon created from 'bold.svg'
    onAction: (_) => {
      editor.insertContent('&nbsp;<strong>It\'s my custom bold icon button!</strong>&nbsp;');
    }
  });
  editor.ui.registry.addButton('myButton2', {
    icon: 'italic',    // the 'italic' icon created from 'italic.svg'
    onAction: (_) => {
      editor.insertContent('&nbsp;<strong>It\'s my custom italic icon button!</strong>&nbsp;');
    }
  });
  editor.ui.registry.addButton('myButton3', {
    icon: 'audio',    // the 'audio' icon created from 'audio.svg'
    onAction: (_) => {
      editor.insertContent('&nbsp;<strong>It\'s my custom audio icon button!</strong>&nbsp;');
    }
  });
}
});