How to Structure a Custom jQuery Plugin

Structuring a custom jQuery plugin requires a pattern that prevents namespace conflicts, supports chainability, and allows for customizable configuration. By wrapping your logic inside an Immediately Invoked Function Expression (IIFE) and extending jQuery’s prototype ($.fn), you create a clean, reusable component that integrates seamlessly into any jQuery-powered application.

The Standard Plugin Architecture

The most widely accepted structure for a jQuery plugin uses four primary components: an IIFE for scope isolation, a definition on jQuery.fn, an options object merged with user settings via $.extend(), and a return this.each() loop to preserve chainability.

Here is the complete, production-ready boilerplate:

(function ($) {
    'use strict';

    // 1. Define default options
    const defaults = {
        property: 'value',
        onComplete: null
    };

    // 2. Define the plugin on jQuery.fn
    $.fn.myCustomPlugin = function (options) {
        // Merge defaults with user-provided options
        const settings = $.extend({}, defaults, options);

        // 3. Maintain chainability by returning this.each()
        return this.each(function () {
            const $element = $(this);

            // Avoid duplicate instantiations using jQuery data
            if ($element.data('myCustomPlugin')) {
                return;
            }

            // Private function specific to this instance
            function init() {
                // Plugin logic here
                $element.addClass('initialized');

                // Trigger a callback if provided
                if (typeof settings.onComplete === 'function') {
                    settings.onComplete.call($element);
                }
            }

            // Save plugin instance or state to the element
            $element.data('myCustomPlugin', {
                settings: settings
            });

            init();
        });
    };
})(jQuery);

Key Structural Elements

1. The IIFE and Scoping

Wrapping the plugin in (function ($) { ... })(jQuery); ensures that the $ alias refers strictly to jQuery inside the plugin scope. This eliminates conflicts with other libraries that might assign $ to a different object (such as Prototype or MooTools).

2. Default Options and $.extend()

By defining a private defaults object outside the $.fn method, memory is conserved because the defaults are not recreated on every invocation. The $.extend({}, defaults, options) method creates a shallow copy, combining default values with any overrides supplied by the developer without mutating the original defaults object. For nested configuration objects, use $.extend(true, {}, defaults, options) for a deep copy.

3. Chainability via this.each()

jQuery functions are expected to be chainable (e.g., $('selector').myPlugin().addClass('active')). To support this, return this.each(...). Inside the initial $.fn method, this refers to the jQuery collection itself. Inside the .each() callback, this refers to the individual raw DOM element, which can be wrapped as $(this).

4. Preventing Multiple Initializations

Attaching the plugin instance or state to the DOM element via $.data() (or $element.data()) prevents accidental re-initialization. If the plugin is called on the same element multiple times, checking $element.data('pluginName') allows you to either return early or direct subsequent calls to public plugin methods.