API Docs

Creating plugins

Everything outside the core is a plugin: thumbnails, zoom, video, autoplay and the rest. The same contract is open to you.

This page covers plugins for the vanilla lightgallery package. The native packages have their own plugin shapes that fit their frameworks: React and Vue plugins are objects with render slots and a hook, and Angular plugins are withName() feature functions. Each package exports the types for writing one, LgPlugin, LgPluginSlots and PluginContext in React, LgVuePlugin, LgPluginSlots and LgPluginContext in Vue, LgFeature and LgPluginContext in Angular, and its built-in plugins are the reference implementations. Logic that has no DOM (math, state, URL handling) belongs in @lightgallery/headless so every package can share it.

The contract

A plugin is a class. lightGallery constructs it with the gallery instance and its DOM utility, then calls a few well-known methods at the right moments:

MethodWhen it runs
constructor(instance, $LG)Once, while the gallery is being set up. Store the instance and merge your defaults with the user’s settings. Do not touch the DOM yet.
init()After the gallery structure exists and every plugin is constructed. Add your UI, subscribe to events.
closeGallery()Optional. Each time the gallery closes. Pause timers, reset state.
destroy()When the gallery is destroyed. Remove your UI and listeners.

Pass the class in the plugins setting and lightGallery does the rest:

lightGallery(document.getElementById('gallery'), {
    plugins: [lgZoom, lgThumbnail, MyPlugin],
});

A complete plugin

A small but real one: a toolbar button that shows the slide position and keeps it current as the user navigates.

// lg-counter.ts
import { lGEvents } from '../../lg-events';
import { LgQuery } from '../../lgQuery';
import { LightGallery } from '../../lightgallery';
import { CounterSettings, counterSettings } from './lg-counter-settings';

export default class Counter {
    core: LightGallery;
    settings: CounterSettings;
    private $LG: LgQuery;

    constructor(instance: LightGallery, $LG: LgQuery) {
        this.core = instance;
        this.$LG = $LG;
        // Plugin defaults first, then whatever the user passed.
        this.settings = { ...counterSettings, ...this.core.settings };
    }

    init(): void {
        if (!this.settings.counter) {
            return;
        }
        this.core.$toolbar.append(
            `<button type="button" class="lg-icon lg-counter-btn" aria-live="polite">${this.label()}</button>`,
        );
        // Namespace your listeners so `destroy()` can remove only yours.
        this.core.LGel.on(`${lGEvents.afterSlide}.counter`, () => {
            this.core.outer.find('.lg-counter-btn').html(this.label());
        });
    }

    label(): string {
        const template = this.settings.counterTemplate;
        return template
            .replace('{index}', String(this.core.index + 1))
            .replace('{total}', String(this.core.galleryItems.length));
    }

    closeGallery(): void {
        // Nothing to pause here; this is where a timer plugin would stop.
    }

    destroy(): void {
        this.core.LGel.off('.counter');
        this.core.outer.find('.lg-counter-btn').remove();
    }
}

The settings live in their own file, typed and with defaults:

// lg-counter-settings.ts
export interface CounterSettings {
    /** Enable the counter plugin. */
    counter: boolean;
    /** `{index}` and `{total}` are replaced with the slide position. */
    counterTemplate: string;
}

export const counterSettings: CounterSettings = {
    counter: true,
    counterTemplate: '{index} of {total}',
};

What the plugin has access to:

  • this.core, the gallery instance: index, galleryItems, settings, outer (the .lg-outer element), $toolbar, $content, and every public method.
  • this.core.LGel, the element the gallery was initialized on. All events fire on it; subscribe with a namespace (${lGEvents.afterSlide}.counter) so you can off('.counter') later.
  • this.$LG, a tiny DOM utility (find, on, off, addClass, html, css, …) so plugins need no dependencies.

Settings, strings and icons

  • Merge your defaults under the user’s settings as in the example; lightGallery does not merge plugin settings for you.
  • Labels that users may want to translate belong in strings. Read them from this.core.settings.strings so one localized object covers the core and every plugin.
  • The built-in plugins register their SVG artwork with this.core.registerDefaultIcons(...) so the icons setting can replace it by name. A plugin outside this repository draws its own buttons; put the SVG in your markup and keep it currentColor-friendly so it follows the toolbar’s colors.

Styles

Plugin styles are separate files, so consumers pay only for what they use. In this repository a plugin’s stylesheet is src/scss/lg-<name>.scss and ships as lightgallery/css/lg-<name>.css. Outside the repository, ship a CSS file next to your plugin and document the import the same way.

Using a plugin outside this repository

You do not need lightGallery’s build system. Write the class in your own project, type it against the package, and pass it in plugins:

import type { LightGallery } from 'lightgallery';

export default class Ping {
    core: LightGallery;
    constructor(instance: LightGallery) {
        this.core = instance;
    }
    init(): void {
        console.log('gallery ready', this.core.galleryItems.length);
    }
    destroy(): void {}
}

Adding a plugin to this repository

If you are contributing a plugin to lightGallery itself, the build picks it up from one registry:

  1. Create src/plugins/<name>/lg-<name>.ts and src/plugins/<name>/lg-<name>-settings.ts (and src/scss/lg-<name>.scss if it needs styles).
  2. Register the entry in plugins-config-rollup.json:
{
    "name": "counter",
    "folder": "plugins/counter/",
    "fileName": "lg-counter"
}
  1. Add the settings type to LightGalleryAllSettings and MobileSettings in src/lg-settings.ts, so plugins: [...] users get typed settings.
  2. Add the package stub packages/plugins/<name>/package.json (copy an existing one) and the ./plugins/<name> entry in the root package.json exports map. Both point at the files the build emits.
  3. Try it in the vanilla dev rig: import it in dev-vanilla/main.ts and run npx vite --config dev-vanilla/vite.config.ts.
  4. npm run build produces dist/plugins/<name>/ with ES, UMD and minified bundles plus types; npm test runs the suites in test/.

Then mirror it: a plugin in the vanilla package should exist in the React, Vue and Angular packages too, with the same settings and behavior.

Edit this page on GitHub