From camunda-skills
Build custom Camunda 8 connectors — outbound/inbound — via JSON template on a protocol connector (no Java) or Java SDK with annotation-driven templates. Helps when OOTB catalog doesn't cover the integration.
How this skill is triggered — by the user, by Claude, or both
Slash command
/camunda-skills:camunda-connectors-developmentThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Build a custom Camunda 8 connector when the OOTB catalog doesn't cover the integration. Two distinct paths share the same element-template surface but differ in whether you write Java.
Build a custom Camunda 8 connector when the OOTB catalog doesn't cover the integration. Two distinct paths share the same element-template surface but differ in whether you write Java.
c8ctl element-template get <id> to fetch the base OOTB template as a starting pointWalk camunda-development first. The short version of the matrix as it applies here:
The two directions are not symmetric — an inbound connector is the only way an external event drives a process from outside the engine. Workers cannot replace inbound; the connector framework owns it.
Three inbound flavours, each owned by a different SDK shape:
See references/connector-sdk-inbound.md for the lifecycle (activate / deactivate) and the correlateWithResult contract that funnels events into the engine.
| Path | When to pick |
|---|---|
| Path A — JSON-only template on a protocol connector (ref) | Outbound integration that is a single call over a supported protocol (REST/SOAP/GraphQL/Kafka/RabbitMQ/AWS messaging). Domain-friendly UI in Modeler without writing Java. Ships as a single JSON file in the repo. |
| Path B — Custom Java connector via the SDK ([refs: outbound, inbound]) | Anything Path A can't reach: multi-step orchestration, proprietary protocols, non-HTTP I/O, inbound triggers (webhook/subscription/polling), or logic that needs reuse across processes as a versioned artefact. Requires Java 17+ and a hosting decision. |
A JSON template is also the natural way to give a job worker a polished Modeler UI when Path A's protocol overlap isn't there and Path B is too heavy — hand-author the template using references/element-template-json.md and bind it to the worker's zeebe:taskDefinition type. That's still a worker, not a connector.
Both paths produce an element template JSON file. The schema is the same; what differs is how the file gets written:
c8ctl element-template get <id> as the starting point, then customise — hide infrastructure properties (URL, method, auth) with "type": "Hidden", pre-fill them with FEEL, and expose only the domain-specific inputs.@ElementTemplate and let the Maven plugin generate the JSON during build (references/element-template-generator.md), or hand-author it.The "hide infrastructure, expose only domain inputs" pattern works for any OOTB template, not just the generic protocol ones. As long as the customised template still writes the input mappings the underlying job worker expects (the zeebe:taskDefinition type and zeebe:input shape are the data contract), the worker sees the same payload — only the Modeler UI differs.
Concrete examples in camunda/connectors:
connectors/openai/element-templates/openai-connector.json — full OpenAI template; a specialised version could hide the system prompt or model picker and expose only domain inputs.connectors/github/element-templates/github-connector.json — specialised UI over the underlying job worker.connectors/github/element-templates/github-webhook-connector-receive.json — the generic webhook inbound connector exposed as a GitHub-specific webhook UI.connectors/hugging-face/element-templates/hugging-face-connector.json — Hugging Face-shaped UI over an underlying REST call.Use c8ctl element-template get <id> to pull the base template, then hide / hardcode / rename properties while preserving the bindings the worker reads.
references/element-template-json.md is the schema reference for both — property types, binding types, FEEL/constraints/condition/generatedValue features, and the template variants for each BPMN attachment.
Field-ordering rule: any property used in another property's FEEL
valuemust appear earlier in thepropertiesarray. Out-of-order references silently evaluate tonull. The same rule applies to Path A customisation and to hand-authored Path B templates.
{{secrets.NAME}} placeholders at execution time. Never store secrets in the BPMN, the template defaults, or hard-coded strings. The standalone runtime reads from SECRET_* environment variables by default (8.9+).getText, base64, and createLink to transform values before the connector receives them.resultVariable, resultExpression) and the Error handling group (errorExpression) via element template headers. See references/element-template-json.md for the binding shapes.Path B has a hosting decision that Path A doesn't — the JAR has to run somewhere. Four options, covered in references/registration-and-hosting.md:
camunda/connectors:X.Y.Z Docker image with custom connector JARs mounted into its app directory.spring-boot-starter-camunda-connectors starter; your application hosts both connectors and your own business code.SPI vs. Spring Bean registration is independent of the hosting choice but constrains it. SPI registration writes a META-INF/services/... file (default behaviour of the Maven plugin) and works in the standalone runtime. Spring Bean registration (@Component on the connector class) needs the embedded Spring Boot runtime and requires <writeMetaInfFileGeneration>false</writeMetaInfFileGeneration> on the Maven plugin to suppress the SPI file — both mechanisms registering the same class causes duplicate discovery.
When Path B will run via the standalone runtime against a SaaS cluster (Hybrid hosting), the Maven plugin's <generateHybridTemplates>true</generateHybridTemplates> flag emits a parallel *-hybrid.json template per connector. The hybrid template flips the connector's task type to a job-worker style binding the SaaS engine can pick up and route to the standalone runtime. Without it, deploying the standard template to SaaS produces a runtime that can never receive jobs.
c8ctl element-template search "<keyword>" and c8ctl element-template get-properties <id> against the live OOTB catalog before naming any ID, group, or property in code or docs.<writeMetaInfFileGeneration>false</writeMetaInfFileGeneration> when using Spring Bean registration — the SPI file and @Component register the same class twice; the runtime instantiates two instances and the second silently shadows the first.value references a sibling declared after it evaluates to null at runtime. The Modeler doesn't flag this; the connector just gets an empty input.<generateHybridTemplates>true</generateHybridTemplates> and deploy the -hybrid variant.activate(InboundConnectorContext) / deactivate(), not handler-per-job.For detail, read from references/:
OutboundConnectorProvider + @Operation (modern), OutboundConnectorFunction (legacy), @Variable / @Header, ConnectorException, Jakarta ValidationInboundConnectorExecutable, three flavours, lifecycle, correlateWithResult / CorrelationResult@ElementTemplate annotation, generateHybridTemplates, versionHistoryEnabled, writeMetaInfFileGenerationGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub camunda/skills --plugin camunda-skills