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:
| Method | When 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-outerelement),$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 canoff('.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 fromthis.core.settings.stringsso one localized object covers the core and every plugin. - The built-in plugins register their SVG artwork with
this.core.registerDefaultIcons(...)so theiconssetting can replace it by name. A plugin outside this repository draws its own buttons; put the SVG in your markup and keep itcurrentColor-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:
- Create
src/plugins/<name>/lg-<name>.tsandsrc/plugins/<name>/lg-<name>-settings.ts(andsrc/scss/lg-<name>.scssif it needs styles). - Register the entry in
plugins-config-rollup.json:
{
"name": "counter",
"folder": "plugins/counter/",
"fileName": "lg-counter"
}
- Add the settings type to
LightGalleryAllSettingsandMobileSettingsinsrc/lg-settings.ts, soplugins: [...]users get typed settings. - Add the package stub
packages/plugins/<name>/package.json(copy an existing one) and the./plugins/<name>entry in the rootpackage.jsonexportsmap. Both point at the files the build emits. - Try it in the vanilla dev rig: import it in
dev-vanilla/main.tsand runnpx vite --config dev-vanilla/vite.config.ts. npm run buildproducesdist/plugins/<name>/with ES, UMD and minified bundles plus types;npm testruns the suites intest/.
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.
lightGallery