From graft
Jenkins plugin core architecture — extension points, Descriptor/Describable pattern, Stapler web framework, configuration binding, annotations, persistence, and project structure. Use this skill when writing, reviewing, or understanding Jenkins plugin Java code. Covers the foundational APIs and patterns every Jenkins plugin builds on: @Extension, @DataBoundConstructor, @DataBoundSetter, @Symbol, BuildStepDescriptor, GlobalConfiguration, and XStream persistence. Make sure to use this skill whenever the user mentions Jenkins plugin development, hpi files, Jenkins build steps, Jenkins global configuration, Manage Jenkins page, Jenkins XML persistence, or any Jenkins extension point — even if they just say "write a Jenkins plugin." Triggers on: Jenkins plugin, extension point, Descriptor, Describable, Stapler, DataBoundConstructor, config.jelly, hpi, Jenkins API, pom.xml Jenkins, Jenkins build step, GlobalConfiguration, Manage Jenkins, Jenkins persistence, SimpleBuildStep.
How this skill is triggered — by the user, by Claude, or both
Slash command
/graft:jenkins-architectureThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Core knowledge for building Jenkins plugins. Everything here applies to all plugin types —
Core knowledge for building Jenkins plugins. Everything here applies to all plugin types — build steps, post-build actions, global configuration, Pipeline steps, and custom extensions.
my-plugin/
pom.xml # Parent POM: org.jenkins-ci.plugins:plugin
src/main/java/ # Java source
src/main/resources/ # Jelly views, help files, messages.properties
src/test/java/ # JUnit tests
src/test/resources/ # Test data, @LocalData, Pipeline .groovy scripts
work/ # Dev Jenkins state (hpi:run), gitignored
Parent POM (must be recent):
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.88</version>
<relativePath />
</parent>
BOM for dependency alignment — eliminates version conflicts:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-2.462.x</artifactId>
<version>...</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Choosing the right BOM line: The BOM artifact name encodes the minimum Jenkins LTS version
(e.g., bom-2.462.x targets Jenkins 2.462.x LTS). Match it to the <jenkins.version> in
your parent POM. Check jenkinsci/bom for available lines.
When starting a new plugin, use the latest LTS BOM. When maintaining an existing plugin, match
the BOM to the project's declared minimum Jenkins version.
Build commands:
| Command | Purpose |
|---|---|
mvn verify | Build + static analysis + tests |
mvn hpi:run | Launch dev Jenkins at localhost:8080/jenkins/ |
mvn hpi:run -Dport=5000 | Custom port |
mvn package | Produce .hpi file |
Extension points are interfaces/abstract classes that model Jenkins behavior. Plugins provide
implementations annotated with @Extension for automatic discovery via ExtensionList.
| Class | Purpose | When to use |
|---|---|---|
Builder | Build-time actions | Compile, test, deploy steps |
Recorder | Post-build result collection | Collect stats, mark unstable/failure. Runs before notifiers. |
Notifier | Post-build notifications | Send outcomes (email, Slack). Runs after recorders. |
BuildWrapper | Pre/post environment setup | Wraps entire build (e.g., set env vars, start services) |
SimpleBuildStep | Pipeline-compatible builder | Preferred for new build steps — works in both Freestyle and Pipeline |
SimpleBuildWrapper | Pipeline-compatible wrapper | Preferred for new wrappers |
| Class | Purpose |
|---|---|
GlobalConfiguration | Plugin global settings on Manage Jenkins page |
Action / RunAction2 | Add URLs, sidebar links, views to model objects |
JobProperty<J> | Per-job configuration |
Trigger<J> | Build triggers (cron, SCM polling) |
SCM | Source control integrations |
RunListener | Build lifecycle hooks |
AdministrativeMonitor | Warnings in admin UI |
QueueDecisionHandler | Control build queue behavior |
The central extensibility mechanism — mirrors the Object/Class relationship.
Describable<T> — the configurable instance (a specific Builder config). Gets serialized
to XML. Has getDescriptor() that looks up the singleton via Jenkins.get().getDescriptorOrDie().
Descriptor<T> — the singleton metadata/factory. Responsibilities:
save()/load())doCheckXyz methods)doFillXyzItems methods)Convention: Always a static nested class named DescriptorImpl:
public class MyBuilder extends Builder implements SimpleBuildStep {
private final String serverUrl;
@DataBoundConstructor
public MyBuilder(String serverUrl) {
this.serverUrl = serverUrl;
}
public String getServerUrl() { return serverUrl; }
@DataBoundSetter
public void setTimeout(int timeout) { this.timeout = timeout; }
@Override
public void perform(Run<?, ?> run, FilePath workspace,
Launcher launcher, TaskListener listener) {
listener.getLogger().println("Connecting to " + serverUrl);
}
@Symbol("myStep")
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
@Override
public String getDisplayName() { return "My Build Step"; }
@Override
public boolean isApplicable(Class<? extends AbstractProject> t) {
return true;
}
public FormValidation doCheckServerUrl(@QueryParameter String value) {
if (value.isEmpty()) return FormValidation.error("URL is required");
return FormValidation.ok();
}
}
}
BuildStepDescriptor<T> adds isApplicable() to control which job types the step appears in.
| Annotation | Purpose |
|---|---|
@Extension | Auto-registers class to ExtensionList. Has ordinal() (higher = first) and optional (graceful skip if deps missing). |
@DataBoundConstructor | Marks constructor Stapler uses to create instances from JSON. Parameter names matched to JSON keys via compile-time annotation processing. |
@DataBoundSetter | Optional properties. After constructor runs, remaining JSON properties set via matching setters. |
@Symbol("name") | Short symbolic name for Pipeline DSL. Requires structs plugin dependency. |
@QueryParameter | Injects HTTP query params into doCheck/doFill methods. Has required, fixEmpty. |
@AncestorInPath | Injects ancestor model object from URL path. |
@Restricted(NoExternalUse.class) | Marks internal APIs — not for external plugin use. |
@CheckForNull / @NonNull | Nullability annotations — expected on web-facing methods. |
@POST | Marks methods requiring POST (CSRF protection). Pair with checkMethod="post" in Jelly. |
Keep @DataBoundConstructor parameters minimal (mandatory only). Use @DataBoundSetter for optional:
@DataBoundConstructor
public MyStep(String location) { this.location = location; }
@DataBoundSetter
public void setTimeout(int timeout) { this.timeout = timeout; }
Default values with XStream optimization — store null when value equals default:
@CheckForNull private String stuff;
@NonNull
public String getStuff() {
return stuff == null ? DescriptorImpl.DEFAULT_STUFF : stuff;
}
@DataBoundSetter
public void setStuff(@NonNull String stuff) {
this.stuff = stuff.equals(DescriptorImpl.DEFAULT_STUFF) ? null : stuff;
}
This prevents the snippet generator from emitting redundant empty arguments.
Convention-over-configuration URL routing via object graph traversal.
URL dispatch: Each path segment resolves to getter/method/field:
/jenkins/job/myproject/configure → Jenkins.getJob("myproject").doConfigure()/jenkins/descriptorByName/com.example.MyBuilder/ → the DescriptorImpl singletonMethod conventions:
doXxx(StaplerRequest2, StaplerResponse2) → handles GET/POST to /xxxgetXxx() → traverses to child object at /xxxgetDynamic(String, ...) → catch-all dynamic lookupView dispatch: If no method matches, Stapler looks for Jelly/Groovy views:
index.jelly — root view of an objectconfig.jelly — configuration forms (on Descriptors)global.jelly — global configuration sectionshelp-fieldName.html — inline help for form fieldsconfig.jelly)StaplerRequest2.bindJSON(Class, JSONObject):
@DataBoundConstructor, matches JSON keys to param names@DataBoundSetter methods@PostConstruct methodsJenkins stores data as XML in JENKINS_HOME via XStream serialization.
Rules:
transientreadResolve()save() to persist, load() to restorePersistentDescriptor interface auto-invokes load() on startupGlobalConfiguration pattern:
@Extension
@Symbol("myPluginConfig")
public class MyConfig extends GlobalConfiguration implements PersistentDescriptor {
private String serverUrl;
public String getServerUrl() { return serverUrl; }
@DataBoundSetter
public void setServerUrl(String serverUrl) {
this.serverUrl = serverUrl;
save();
}
}
The @Symbol enables Configuration-as-Code (CasC) support automatically.
| Class | Purpose |
|---|---|
FormValidation | Return type for doCheck methods — .ok(), .warning(msg), .error(msg) |
ListBoxModel | Return type for doFill methods — populates dropdowns |
FilePath | Remote-aware file operations (works across controller/agent) |
Launcher | Remote-aware process execution |
Jenkins | Singleton root object — entry point for everything |
Secret | Encrypted storage for passwords/tokens — never use plain String |
Util | String utilities — fixEmpty(), xmlEscape(), etc. |
Functions | Jelly helper — htmlAttributeEscape(), isWindows(), etc. |
AbstractBuild instead of Run — breaks Pipeline compatibilitySecret class@Symbol — forces ugly [$class: 'ClassName'] Pipeline syntax@DataBoundConstructor — put optional params in @DataBoundSettersave() after GlobalConfiguration changes — data lost on restarttoLowerCase() without Locale.ROOT — locale-dependent behaviornpx claudepluginhub aneveux/claude-garden --plugin graftGenerates declarative, scripted Jenkinsfiles and shared libraries for CI/CD pipelines with Docker/K8s agents, parallel stages, approvals, and security scans.
Configures Maven plugins like compiler, surefire, jar, resources; sets up executions, plugin management, and custom configurations in pom.xml for Java builds.