Skip to content
← Back to blogEngineering

A Plugin Action Contract Prevents Integration Debt

Define a plugin action contract for discovery, authorization, bounded execution, failure handling, and evidence without creating integration debt.

See how it worksPlugin SDK →

A refund button is easy to add. The debt appears when the interface decides when it is visible, the plugin invents its own permission check, and the downstream result never returns to the ticket.

The first plugin is simple. The second becomes special. Soon the interface knows too much. Authorization is duplicated. Nobody can explain why a button exists or what evidence it produces.

Integration debt starts when plugin actions are treated like shortcuts instead of bounded system capabilities.

The Contract Separates Platform Work From Plugin Work

A plugin action needs one contract that keeps control in the platform and target-specific behavior in the plugin.

ConcernPlatform responsibilityPlugin responsibility
DiscoverySupply ticket context and filter actions by policyReturn stable action IDs, labels, and input requirements
AuthorizationEnforce role, status, and approval rules at executionReject an invalid or unsupported target request
RequestValidate a bounded payload and attach an idempotency keyTranslate the request for the downstream system
ExecutionTrack timeout, retry, and cancellation statePerform the target-specific call and return a typed result
EvidenceWrite request, decision, and result to the ticketReturn correlation references and a safe outcome summary

This article is about that boundary. What a plugin action is covers the operator-facing model. Why plugins beat hard-coded workflow branches covers the product architecture decision.

Integration Debt Comes from Unbounded Integration

Plugin actions close the gap between decision and execution. An operator sees a ticket and wants to trigger a downstream workflow without leaving the record.

The debt appears when the integration has no clear shape. The interface knows too much about the plugin. The plugin knows too much about interface state. Authorization is copied into each path. The result is a distributed special case, not a capability.

The right pattern is narrower:

  1. Discover what actions exist.
  2. Decide whether the current user and context may run them.
  3. Execute one action with a bounded payload.
  4. Capture the result in a structured way.

If a plugin action does not fit that model, it does not belong in the operator workflow yet.

Discovery Comes Before Buttons

Good integration design starts with discovery. The platform should ask the plugin what actions are available for the current record. It should not infer them from a static list.

Discovery answers concrete questions:

  • What action IDs exist for this record type?
  • Which actions are available in the current context?
  • What display label and description should the operator see?
  • What inputs, if any, are required before execution?

That matters because actions are usually conditional. A reprocess action may be valid only for certain ticket states. A partner sync action may appear only when a linked record exists.

Discovery should be read-only. If it mutates state, every refresh is risky. If it stays pure, the UI can cache it. It can rerun discovery after state changes. It can render the action list without side effects.

Authorization Belongs at the Edge of Execution

The worst pattern is to show every action and let the plugin reject the ones that should be hidden.

That wastes operator time and leaks implementation detail. It also encourages the false belief that plugin checks are enough.

Authorization should be evaluated twice:

  • At discovery time, it decides what is visible.
  • At execution time, it decides what is allowed.

Those checks can use the same policy inputs. They serve different purposes. Discovery keeps the interface accurate. Execution prevents privilege bypass.

The decision should consider operator role, ticket status, account scope, sensitivity, and any required approval path. This matters for actions that affect external systems. Once a workflow leaves your boundary, it may be expensive or impossible to undo. Visibility is not permission.

The Payload Must Stay Bounded and Intentional

Every plugin action should accept a narrow request shape. The request should contain only what the plugin needs to do the job.

That usually means a stable action identifier, the ticket identifier, the operator identity, a small set of validated inputs, and an idempotency token or request reference. A refund action may accept an amount, currency, reason code, and payment reference. It should not receive the entire interface state.

It should not contain arbitrary UI state, entire object graphs, or undocumented fields 'for flexibility'.

Loose payloads feel fast at first. They cost more later. They create hidden coupling between the UI, the platform, and the plugin implementation.

Bounded payloads make testing practical. You can simulate a plugin with a handful of well-defined inputs instead of reproducing the entire application state.

Execution Should Be an Observable Event

A plugin action is not finished when the request leaves the platform. It is finished when the result is written back into the ticket timeline.

Every execution should produce a structured record. At minimum, the record must name the operator. It must name the action. It must give the start time. It must state success or failure. It must store the external reference returned. It must record what changed in the local record.

This is where integration debt often hides. Teams build the action but not the result capture. Then they rely on vendor dashboards or ad hoc log searches to reconstruct what happened.

If the result is not in the ticket history, it is not operationally complete.

Operators need the full sequence:

  • The action was discovered.
  • The operator selected it.
  • The platform authorized it.
  • The plugin executed it.
  • The platform persisted the result.

That sequence creates a durable audit trail. It reduces support burden when someone asks why a downstream system changed.

Failure Handling Should Be First-Class

Plugin actions fail in predictable ways. The plugin is unavailable. It rejects the request. It times out. It returns success while the ticket cannot be updated.

The platform should distinguish those failures. A generic "failed" state is not enough. Useful categories are denied, rejected, unavailable, timed out, and partial.

This makes retry behavior safer. It makes support triage easier. It helps product teams decide whether an action needs idempotency, backoff, or human follow-up.

Extensibility Requires a Plugin Contract

The cheapest way to create integration debt is to build the first plugin as if it will be the last.

Define a small contract. Enforce it consistently. A healthy action system usually has these properties:

  • Each plugin is discovered through a standard interface.
  • Actions are described with stable IDs and human-readable metadata.
  • Execution requests are validated before dispatch.
  • Authorization is platform-controlled.
  • Results are stored in the ticket record, not scattered across systems.

That gives teams room to add new external capabilities without rewriting the operator experience each time. The contract should be opinionated enough to avoid drift, but flexible enough to support multiple plugins and use cases.

A Practical Rule of Thumb Separates Prototypes from Integrations

If you cannot answer these questions, the integration is too loose:

  • Can the platform discover the action without hard-coding it?
  • Can the platform hide it when the user is not allowed to run it?
  • Can the action run with a narrow, validated payload?
  • Can the result be written back as ticket evidence?
  • Can support teams reconstruct the full history later?

If the answer is no to any of those, the integration is still a prototype.

Reducing Work Without Reducing Control Is the Operating Principle

Plugin actions are valuable when they reduce swivel-chair work without reducing control.

That only happens when the platform owns discovery, authorization, and result capture. Plugins own the bounded external work. When those responsibilities blur, integration debt begins to accumulate.

Keep the contract small. Keep the permissions explicit. Keep the result in the record.

That is how plugin actions stay extensible instead of becoming an operational liability.

Continue exploring
Next product pathPlugin SDKAdd plugin actions without hard-coding every downstream workflow into the core product.Related pathProduct OverviewSee how unified triage, approvals, audit trails, and plugins connect.
Related reads
Building a Latch Plugin That Reads Handwritten ChequesA Latch plugin that reads handwritten fields from a cheque — payee, amount, cheque number — validates them, and gates credit or reject actions on the result.Extend the Platform with Providers Instead of Hard-Coded Custom WorkflowsExtensible plugin architecture lets teams extend workflows safely without brittle custom code or fragmented control logic.What an AI Agent's Token Can ReachConnect an AI agent to a case system through scoped tools, a central authorization gate, and a request-then-approve default for high-impact actions.
Ready to move beyond reading?

See the same workflow running end to end.

Follow one ticket from intake through review and plugin execution. See the request, the decision, and the result in its audit trail.

See how it worksTalk to us