This document defines the interface contract for Infracost provider plugins. A provider plugin takes the IaC-agnostic cost tree extracted by a parser plugin and returns cost estimates and FinOps policy results. The SDK makes no assumption about how prices are obtained — implementations may hardcode prices, query cloud APIs directly, use the Infracost Cloud API, or any other mechanism.
infracost scan
|
v
Parser plugins extract resources into an IaC-agnostic cost tree
|
v
Provider plugins price the tree and evaluate FinOps policies
|
v
CLI aggregates costs, evaluates output, and renders results
Plugins are standalone binaries that communicate with the Infracost CLI over gRPC using the HashiCorp go-plugin framework. The CLI spawns the plugin process, performs a handshake, and issues gRPC calls. When processing is complete the plugin process exits.
Every plugin implements two gRPC services:
PluginService— a singleGetPluginInfoRPC that reports the plugin's type and metadata. This is how the CLI tells provider plugins apart from parser plugins.ProviderService— the pricing RPCs (Process,ListFinopsPolicies).
Both services are registered on the same gRPC server (see Handshake).
The CLI does not infer a plugin's type or identity from its binary filename. It launches every executable in the plugin directory, calls GetPluginInfo, and uses the returned type to decide whether the binary is a parser or a provider. Binaries that fail to launch or handshake are skipped.
A descriptive binary name such as infracost-provider-plugin-<name> is conventional and recommended for clarity, but it is not required for discovery.
The name returned by GetPluginInfo is the plugin's identity. By convention it is <namespace>/<name>:
- Official plugins use the
infracost/namespace, e.g.infracost/aws,infracost/azure,infracost/google. - Community plugins should use their own namespace, e.g.
acme/oracle.
Names must be unique across all installed plugins.
All Infracost plugins — parser and provider alike — share one handshake. The type is resolved at runtime via GetPluginInfo, so there is a single magic cookie rather than a per-type one.
const maxMessageSize = 64 * 1024 * 1024
goplugin.Serve(&goplugin.ServeConfig{
HandshakeConfig: goplugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "INFRACOST_PLUGIN",
MagicCookieValue: "de8c7e96-497c-4168-80c4-fc875c8ce764",
},
Plugins: map[string]goplugin.Plugin{
// The dispense key is always "plugin".
"plugin": yourPluginStruct,
},
GRPCServer: func(opts []grpc.ServerOption) *grpc.Server {
opts = append(opts,
grpc.MaxRecvMsgSize(maxMessageSize),
grpc.MaxSendMsgSize(maxMessageSize),
)
return grpc.NewServer(opts...)
},
})The plugin struct must implement plugin.GRPCPlugin and register both services on the server:
type myPlugin struct {
goplugin.NetRPCUnsupportedPlugin
}
func (p *myPlugin) GRPCServer(_ *goplugin.GRPCBroker, g *grpc.Server) error {
pluginpb.RegisterPluginServiceServer(g, myServiceImplementation)
pluginpb.RegisterProviderServiceServer(g, myServiceImplementation)
return nil
}
func (p *myPlugin) GRPCClient(_ context.Context, _ *goplugin.GRPCBroker, _ *grpc.ClientConn) (interface{}, error) {
return nil, fmt.Errorf("not implemented")
}The generated Go bindings live in github.com/infracost/proto/gen/go/infracost/plugin (aliased pluginpb above). Embedding pluginpb.UnimplementedPluginServiceServer and pluginpb.UnimplementedProviderServiceServer in your service struct keeps it forward-compatible if new RPCs are added.
Plugins implement PluginService and ProviderService, both defined in the infracost.plugin package:
// infracost/plugin/plugin.proto
service PluginService {
rpc GetPluginInfo(GetPluginInfoRequest) returns (GetPluginInfoResponse);
}
// infracost/plugin/provider.proto
service ProviderService {
rpc Process(ProcessRequest) returns (ProcessResponse);
rpc ListFinopsPolicies(ListFinopsPoliciesRequest) returns (ListFinopsPoliciesResponse);
}Returns the plugin's type and static metadata. Called once at startup when the CLI discovers the binary.
Request: Empty.
Response:
| Field | Type | Required | Description |
|---|---|---|---|
type |
PluginType | yes | PROVIDER for provider plugins. (PARSER and PLUGIN_TYPE_UNKNOWN are the other values.) |
name |
string | yes | Plugin identity, e.g. "infracost/aws" or "acme/oracle". Must be unique. |
version |
string | no | Plugin version. Semver recommended but not required. |
description |
string | no | Human-readable description shown in CLI output. |
url |
string | no | Where the plugin is documented/downloaded. |
author |
string | no | Company or individual that authored the plugin. |
Receives the IaC-agnostic cost tree and returns priced resources and FinOps policy results.
Request:
| Field | Type | Description |
|---|---|---|
input |
TreeInput | The cost tree plus usage data, project info, feature flags, and settings. |
The TreeInput message (infracost/provider/tree.proto) includes:
| Field | Type | Description |
|---|---|---|
tree |
tree.Tree | The parser output as an IaC-agnostic tree (providers → services → resources). |
absolute_path |
string | Path to the project being scanned (a directory for Terraform/Terragrunt, a file for CloudFormation). |
project_info |
ProjectInfo | Project name, branch, workspace, production flag. |
previous_resource_addresses |
string[] | Resource addresses seen on a previous run, used to decide which resources are "new". |
usage |
Usage | Usage data for usage-based resources. |
finops_policy_config |
FinopsPolicyConfiguration | Which policies to run and their settings (only relevant if features.enable_finops_policies). |
features |
Features | Feature flags (price lookups, recommendations, policies, environmental metrics). |
settings |
Settings | Currency code, disk cache preferences. |
infracost |
Infracost | Infracost-specific credentials (API key, pricing endpoint, trace/org IDs). Community providers should ignore these. |
Unlike parser plugins, TreeInput has no generic options channel — a provider
plugin that needs its own configuration must use an out-of-band mechanism
(env vars, files, or flags).
Response:
| Field | Type | Description |
|---|---|---|
output |
Output | Priced resources and FinOps policy results. See Output Format. |
Contract:
- Walk
input.tree(providers → services → resources), price the resources you support, and return them inoutput.resources. - Return an empty
Outputrather than an error when there is nothing to price. - Honour
input.settings.currency; convert prices to the requested currency where you can.
Returns the set of FinOps policies this provider can evaluate. Policies are rules like "use GP3 instead of GP2 for EBS volumes" or "avoid oversized instances".
Request: Empty.
Response:
| Field | Type | Description |
|---|---|---|
policies |
FinopsPolicy[] | Available policies. |
Each FinopsPolicy has:
| Field | Type | Description |
|---|---|---|
slug |
string | Unique policy identifier. |
name |
string | Human-readable name. |
group |
string | Grouping/category. |
description |
string | Human-readable description. |
only_new_resources |
bool | Whether the policy applies only to newly added resources. |
Contract:
- Return an empty list if the provider does not evaluate any policies.
- Each policy must have a unique
slug.
The Output message (infracost/provider/output.proto) contains:
| Field | Type | Description |
|---|---|---|
resources |
Resource[] | Normalised, priced, IaC-agnostic resources. |
finops_results |
FinopsPolicyResult[] | Per-policy pass/fail results. |
| Field | Type | Description |
|---|---|---|
id |
string | Unique resource ID from the parser. |
type |
string | Resource type (e.g. aws_instance, AWS::EC2::Instance). |
name |
string | Full resource address. |
region |
string | Cloud region (e.g. us-east-1). |
metadata |
ResourceMetadata | Filename, line numbers, checksums. |
is_supported |
bool | Whether this provider can price the resource. |
is_free |
bool | Whether the resource has no associated costs. |
is_provider_supported |
bool | Whether the resource's provider is supported at all. |
action |
ResourceAction | Whether the resource was added/modified/deleted/unchanged this run. |
costs |
ResourceCosts | Cost components for this resource. |
tagging |
Tagging | Resource-level tag information. |
child_resources |
Resource[] | Nested resources (e.g. a disk belonging to a VM). |
Each resource's costs.components is a list of CostComponent messages:
| Field | Type | Description |
|---|---|---|
name |
string | Display name (e.g. "Compute (t3.small, on-demand)"). |
unit |
string | Unit of measurement (e.g. "hours", "GB", "requests"). |
usage_based |
bool | Whether the price depends on usage data. |
price_not_found |
bool | True if no price was found (still return the component). |
price_was_hardcoded |
bool | True if the price was hardcoded rather than looked up dynamically. |
period_price |
PeriodPrice | Price per unit per period. |
quantity |
Rat | Number of units. |
discount_rate |
Rat | Discount rate (0.0 = none, 0.3 = 30% off). |
environmental_metrics |
EnvironmentalMetrics | Optional carbon/water metrics. |
| Field | Type | Description |
|---|---|---|
price |
Rat | The price as a rational number. |
period |
Period | MONTH or HOUR. |
Prices and quantities use infracost.rational.Rat to avoid floating-point precision issues:
| Field | Type | Description |
|---|---|---|
numerator |
bytes | Big-endian unsigned integer bytes. |
denominator |
bytes | Big-endian unsigned integer bytes. |
negative |
bool | Whether the value is negative. |
For example, $0.0116/hr can be represented as numerator=116, denominator=10000.
The provider plugin interface is deliberately agnostic about how prices are obtained. A provider may:
- Hardcode prices — Simplest approach, suitable for stable or internal pricing
- Query cloud APIs — Call AWS Pricing API, Azure Retail Prices API, etc. directly
- Use a pricing database — Query a local or remote database of prices
- Use the Infracost Cloud API — The official Infracost providers use this approach
- Combine approaches — e.g., hardcode some prices and look up others dynamically
TreeInput.settings includes a currency code so the provider can return prices in the user's preferred currency. TreeInput.infracost contains Infracost-specific credentials that only apply to providers using the Infracost Cloud API — community providers should ignore these fields.
The CLI discovers plugins by scanning a plugin directory, which defaults to os.UserCacheDir()/infracost/plugins:
- Linux:
~/.cache/infracost/plugins - macOS:
~/Library/Caches/infracost/plugins - Windows:
%LocalAppData%\infracost\plugins
Drop your built binary in that directory and run infracost against a project. The CLI will launch the binary, call GetPluginInfo, and route the cost tree to it if it reports type: PROVIDER.
Because the gRPC contract is plain Go, the most reliable way to test a provider is with Go unit tests that build a TreeInput and call Process / ListFinopsPolicies directly.
- Max gRPC message size: 64 MB (both send and receive)
- Binary size: no hard limit, but aim for fast downloads
- Concurrency: the CLI may run plugins in parallel; plugins are separate processes, so keep any shared on-disk state (caches) concurrency-safe
For a minimal starting point, see the example/ directory in this repo.
The official Infracost provider plugins (AWS, Azure, Google, Kubernetes) follow this same contract as separate per-cloud binaries, each scoped to one cloud's pricing and policies.