nebula-logger-plugin-development

Builds custom plugins that extend Nebula Logger to track and send logs where you need them.

Installation
Run `npx skills add "https://github.com/jongpie/nebulalogger" --skill "nebula-logger-plugin-development"` to install this skill, then follow its SKILL.md instructions for my next request.

Paste this into Claude Code, Cursor, or any agent that can run commands.

SKILL.mdShow the author's original SKILL.md
---
name: nebula-logger-plugin-development
description: Use this skill when the user wants to build a new plugin that extends Nebula Logger - for example, custom trigger handlers on `LogEntryEvent__e` / `Log__c` / `LogEntry__c`, custom purge actions, or Slack-style outbound integrations. Covers the plugin framework interfaces, `LoggerPlugin__mdt` configuration, package layout, and testing considerations.
---

# Building Plugins for Nebula Logger

## Package Compatibility

The plugin framework is **only available in the unlocked package**. The managed package (namespace `Nebula`) does not currently expose the plugin extension points.

If you plan to write plugins, install the unlocked package. See [nebula-logger-install](../nebula-logger-install/SKILL.md) for the choice.

Note that `LoggerPlugin`, `LoggerPlugin.Triggerable`, `LoggerPlugin.Batchable`, `LoggerTriggerableContext`, and `LoggerBatchableContext` are all `public` (not `global`). Everywhere else in this skill collection, the guidance is "only rely on `global`" (see the "Supported API Surface" section of [nebula-logger-instrumentation](../nebula-logger-instrumentation/SKILL.md)). The plugin framework is a deliberate exception: the extension points are `public` because they're only reachable inside the unlocked package's own compilation unit. As with any `public` surface, breaking changes are possible - pin plugins to a Nebula Logger version you've tested against and re-verify after upgrades.

## Plugin Framework Overview

Nebula Logger's plugin framework lets you register Apex classes to run inside two extension points:

1. **Trigger extension point** - Runs during `LoggerSObjectHandler` execution on `LogEntryEvent__e`, `Log__c`, `LogEntry__c`, `LogEntryTag__c`, `LoggerScenario__c`, and `LoggerTag__c` triggers. Use this to add fields, enrich data, or send external notifications when a log is created.
2. **Batch extension point** - Runs during `LogBatchPurger` execution. Use this to archive logs to an external system, apply custom purge policies, or emit metrics on purged records before they're deleted.

Both extension points are driven by `LoggerPlugin__mdt` records. Adding a plugin means: writing an Apex class that implements the right interface (or both, if the plugin has trigger and batch responsibilities), then creating a `LoggerPlugin__mdt` record that points at it.

**Strongly prefer Apex over Flow for both extension points.** The framework technically supports Flow-based plugins via `SObjectHandlerFlowName__c` / `BatchPurgerFlowName__c`, but Flow support currently has known issues with no fixed ETA - it may be deprecated altogether in a future release. New plugins should implement the Apex interfaces below and leave the Flow fields on the `LoggerPlugin__mdt` record blank.

## The Two Interfaces

Both interfaces live on `LoggerPlugin` in the `Nebula Logger - Core` package.

### `LoggerPlugin.Triggerable` (trigger extension point)

Implement one method:

```apex
void execute(LoggerPlugin__mdt configuration, LoggerTriggerableContext input);
```

- `configuration` is the `LoggerPlugin__mdt` record that registered the plugin - use it to read plugin-specific settings.
- `input` describes the current trigger context and exposes `sobjectType`, `triggerOperationType`, `triggerNew`, `triggerNewMap`, `triggerOldMap`, and a `triggerRecords` list that pairs old/new record versions per record (populated for Flow-friendly access).

### `LoggerPlugin.Batchable` (batch extension point)

Implement three methods matching the `Database.Batchable` lifecycle:

```apex
void start(LoggerPlugin__mdt configuration, LoggerBatchableContext input);
void execute(LoggerPlugin__mdt configuration, LoggerBatchableContext input, List<SObject> scopeRecords);
void finish(LoggerPlugin__mdt configuration, LoggerBatchableContext input);
```

`scopeRecords` is the current batch of records `LogBatchPurger` is about to process. `LogBatchPurger` walks the object hierarchy from the lowest level up (`LogEntryTag__c`, then `LogEntry__c`, then `Log__c`), filtered to records whose parent `Log__c.LogRetentionDate__c` is past-due. Check `input.sobjectType` if the plugin behavior needs to differ per object.

## `LoggerPlugin__mdt` Configuration

Every plugin gets one `LoggerPlugin__mdt` record. Key fields:

| Field                                             | Purpose                                                                                                                 |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `DeveloperName` / `Label`                         | Identify the plugin.                                                                                                    |
| `IsEnabled__c`                                    | Toggle without deleting the record. Marked as `SubscriberControlled` so admins can flip it in installed orgs.           |
| `SObjectHandlerApexClass__c`                      | Apex class name implementing `LoggerPlugin.Triggerable`. Set this to register the class at the trigger extension point. |
| `SObjectHandlerFlowName__c`                       | Flow API name for a Flow-based trigger plugin. Leave null - see the "prefer Apex" note above.                           |
| `SObjectHandlerExecutionOrder__c`                 | Integer that orders multiple plugins on the same handler. Lower numbers run first. Ties are broken by developer name.   |
| `BatchPurgerApexClass__c`                         | Apex class name implementing `LoggerPlugin.Batchable`. Set this to register the class at the batch extension point.     |
| `BatchPurgerFlowName__c`                          | Flow API name for a Flow-based batch plugin. Leave null - see the "prefer Apex" note above.                             |
| `BatchPurgerExecutionOrder__c`                    | Ordering across multiple batch plugins.                                                                                 |
| `Description__c` / `Link__c` / `VersionNumber__c` | Metadata surfaced in the Logger Console for admin awareness.                                                            |

Set whichever combination of the Apex class fields matches what the class implements: `SObjectHandlerApexClass__c` for a `LoggerPlugin.Triggerable`, `BatchPurgerApexClass__c` for a `LoggerPlugin.Batchable`, or both fields pointing at the same class when it implements both interfaces (like `LogEntryArchivePlugin`). Leave the Flow fields null.

## Plugin Folder Layout

Existing plugins under `nebula-logger/plugins/<name>/` follow a consistent layout. When scaffolding a new plugin, mirror it:

```
nebula-logger/plugins/<name>/
  plugin/
    classes/               # Apex classes (both implementation and *_Tests.cls test classes)
    customMetadata/        # LoggerPlugin.<Name>.md-meta.xml
    objects/               # Any plugin-specific custom objects/fields (optional)
    permissionsets/        # <Name>PluginAdmin.permissionset-meta.xml (optional)
    testSuites/            # <Name>Plugin.testSuite-meta.xml grouping the plugin's test classes
  README.md
```

Test classes live in the same `plugin/classes/` folder as the code under test - they aren't split into a separate top-level `tests/` folder. The Slack plugin's `plugin/slack/` subfolder is a plugin-specific quirk from packaging around some shared `core/main` metadata; new plugins should drop the extra nesting and put everything directly under `plugin/`.

The `LoggerPlugin.<Name>.md-meta.xml` file is the registration record - populate `SObjectHandlerApexClass__c` and/or `BatchPurgerApexClass__c` per the guidance above. Existing plugins to reference:

- `nebula-logger/plugins/slack/` - trigger plugin that sends Slack notifications.
- `nebula-logger/plugins/log-retention-rules/` - trigger plugin that sets `LogRetentionDate__c` based on CMDT rules.
- `nebula-logger/plugins/big-object-archiving/` - implements BOTH interfaces on a single class (`LogEntryArchivePlugin`) - the trigger side captures records to the big object, the batch side archives before purge.
- `nebula-logger/plugins/async-failure-additions/` - adds logging for `FlowExecutionErrorEvent`, unexpected batch failures, and Queueable finalizers.

## Ordering and Composition

- Trigger plugins run **after** the built-in `LoggerSObjectHandler` logic for that object.
- Multiple plugins on the same handler run in `SObjectHandlerExecutionOrder__c` order (nulls last, then by `DeveloperName`).
- Plugins that mutate the trigger records (adding tags, populating fields) should run before plugins that publish outbound notifications, so notifications see the enriched data.

## Testing Plugins

- Ship each plugin with a matching `<PluginClass>_Tests.cls` alongside the implementation class in `plugin/classes/`.
- Add the plugin's test classes to a `<Name>Plugin.testSuite-meta.xml` under `plugin/testSuites/` (see `LoggerLogEntryArchivePlugin.testSuite-meta.xml` and `LoggerLogRetentionRulesPlugin.testSuite-meta.xml` for the pattern). The suite becomes the target of a `sf apex run test --suite-names <Name>Plugin` invocation. Only the core package currently has a dedicated `npm run test:apex:suite:core` script; plugin suites are exercised through `npm run test:apex` (which runs `RunLocalTests`) or an ad-hoc `sf apex run test --suite-names` call.
- For trigger plugins, construct a `LoggerTriggerableContext` manually with the trigger records you want to exercise, then call `execute(configuration, input)` directly. Do not require a real trigger fire in tests.
- For batch plugins, construct a `LoggerBatchableContext` and pass a synthesized `scopeRecords` list. Assert on the side effects (DML, callouts via `System.Test.setMock`, published events).

## New Plugin Checklist

Before shipping a new plugin:

1. Apex class implements at least one of `LoggerPlugin.Triggerable` / `LoggerPlugin.Batchable` - both is fine when a single class needs to react to trigger events and participate in the purge batch.
2. `LoggerPlugin.<Name>.md-meta.xml` exists, has `IsEnabled__c=true` (or `false` if opt-in), and populates the class-name field(s) that match the interfaces the class implements: `SObjectHandlerApexClass__c` for `Triggerable`, `BatchPurgerApexClass__c` for `Batchable`, or both fields when the class implements both interfaces.
3. `<Name>PluginAdmin.permissionset-meta.xml` grants full access on plugin-owned objects and CMDT.
4. `<PluginClass>_Tests.cls` covers happy path, empty scope, and disabled-configuration cases.
5. `README.md` in the plugin folder documents install steps and any prerequisites.
6. `sfdx-project.json` has a `packageDirectories` entry for the plugin so it can be built and installed as its own unlocked package.

## Related Skills

- [nebula-logger-console](../nebula-logger-console/SKILL.md) - The `LoggerPlugin__mdt` list view in the console is where admins toggle plugins on and off.
- [nebula-logger-purging-and-retention](../nebula-logger-purging-and-retention/SKILL.md) - Where `LoggerPlugin.Batchable` plugins slot in.

Mirrored from the author's public source. Install counts from the open skills registry.

The systems behind these skills get built for partners every week.

Partner with us