Skip to main content
Halo’s plugin system is built on top of PF4J, a powerful plugin framework for Java. Plugins allow you to extend Halo’s functionality without modifying the core application.

What is a Plugin?

A plugin is a standalone module that extends Halo’s capabilities by:
  • Adding custom content types (Extensions)
  • Creating custom API endpoints
  • Handling events and implementing business logic
  • Providing UI components and static resources
  • Integrating with external services

Core Concepts

BasePlugin

Every plugin must extend the BasePlugin class, which serves as the entry point for your plugin:
package run.halo.app.plugin;

import org.pf4j.Plugin;

public class BasePlugin extends Plugin {
    protected PluginContext context;

    public BasePlugin(PluginContext pluginContext) {
        this.context = pluginContext;
    }
}

PluginContext

The PluginContext provides information about your plugin and facilitates communication with the application:
public class PluginContext {
    private final String name;           // Plugin name
    private final String configMapName;  // ConfigMap name for settings
    private final String version;        // Plugin version
    private final RuntimeMode runtimeMode; // development or deployment
}
PF4J recommends using PluginContext instead of PluginWrapper for better encapsulation.

Plugin Metadata

Plugins are defined using the @GVK annotation and YAML descriptors.

The @GVK Annotation

Extensions and custom resources use the @GVK annotation to define their metadata:
@GVK(
    group = "plugin.halo.run",
    version = "v1alpha1",
    kind = "Plugin",
    plural = "plugins",
    singular = "plugin"
)
public class Plugin extends AbstractExtension {
    // Plugin implementation
}
Parameters:
  • group: The API group (e.g., plugin.halo.run, my-plugin.example.com)
  • version: API version (e.g., v1alpha1, v1beta1, v1)
  • kind: The resource type name (e.g., Plugin, Post, Comment)
  • plural: Plural form for API endpoints (e.g., plugins, posts)
  • singular: Singular form for display (e.g., plugin, post)

GroupVersionKind (GVK)

The GVK uniquely identifies an Extension type:
public record GroupVersionKind(String group, String version, String kind) {
    // Creates a GVK from apiVersion and kind
    public static GroupVersionKind fromAPIVersionAndKind(
        String apiVersion, 
        String kind
    ) {
        // apiVersion format: "group/version" or just "version"
        var gv = GroupVersion.parseAPIVersion(apiVersion);
        return new GroupVersionKind(gv.group(), gv.version(), kind);
    }
}

Extension Interface

The Extension interface is the foundation for all custom resources in Halo:
public interface Extension extends ExtensionOperator, Comparable<Extension> {
    // Extensions are comparable by metadata.name
}

AbstractExtension

Most Extensions extend AbstractExtension for convenience:
public abstract class AbstractExtension implements Extension {
    private String apiVersion;  // e.g., "plugin.halo.run/v1alpha1"
    private String kind;        // e.g., "Plugin"
    private MetadataOperator metadata; // name, labels, annotations, etc.
}

Creating Your First Plugin

1
Create a plugin class
2
Extend BasePlugin to create your main plugin class:
3
package com.example.myplugin;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import run.halo.app.plugin.BasePlugin;
import run.halo.app.plugin.PluginContext;

@Slf4j
@Component
public class MyPlugin extends BasePlugin {

    public MyPlugin(PluginContext pluginContext) {
        super(pluginContext);
    }
}
4
Define the plugin descriptor
5
Create plugin.yaml in your resources directory:
6
apiVersion: plugin.halo.run/v1alpha1
kind: Plugin
metadata:
  name: my-plugin
spec:
  version: 1.0.0
  requires: ">=2.0.0"
  author:
    name: Your Name
    website: https://example.com
  logo: https://example.com/logo.png
  homepage: https://github.com/yourusername/my-plugin
  displayName: My Awesome Plugin
  description: A plugin that does amazing things
  license:
    - name: MIT
      url: https://opensource.org/licenses/MIT
7
Add Spring components
8
Use Spring’s @Component, @Service, or @Configuration annotations:
9
@Service
public class MyService {
    public void doSomething() {
        // Your business logic
    }
}
10
Use dependency injection
11
Inject Halo services into your components:
12
@Component
public class MyComponent {
    private final ExtensionClient client;

    public MyComponent(ExtensionClient client) {
        this.client = client;
    }

    public void listPlugins() {
        var plugins = client.list(Plugin.class, null, null);
        // Work with plugins
    }
}

Plugin Descriptor Fields

The plugin.yaml file defines your plugin’s metadata:
FieldTypeRequiredDescription
apiVersionStringYesAlways plugin.halo.run/v1alpha1
kindStringYesAlways Plugin
metadata.nameStringYesUnique plugin identifier
spec.versionStringYesSemantic version
spec.requiresStringNoHalo version requirement (default: *)
spec.author.nameStringYesAuthor name
spec.author.websiteStringNoAuthor website
spec.logoStringNoPlugin logo URL
spec.homepageStringNoPlugin homepage
spec.repoStringNoSource repository URL
spec.displayNameStringNoDisplay name for UI
spec.descriptionStringNoPlugin description
spec.licenseArrayNoLicense information
spec.pluginDependenciesMapNoOther plugin dependencies

Development vs Production

The PluginContext provides the runtime mode:
@Component
public class MyPlugin extends BasePlugin {
    
    public MyPlugin(PluginContext context) {
        super(context);
        
        if (context.getRuntimeMode().isDevelopment()) {
            log.info("Running in development mode");
        }
    }
}

Best Practices

  • Use semantic versioning for your plugin
  • Provide clear descriptions and documentation
  • Follow Spring Boot conventions for component organization
  • Use constructor injection instead of field injection
  • Handle errors gracefully and provide meaningful messages
Do not modify the PluginContext instance. It’s provided for read-only access to plugin information.

Next Steps

Build docs developers (and LLMs) love