Skip to main content

Interfaces

Dagger interfaces let a function accept or return any object that exposes a required set of functions, without coupling the function to a specific concrete object type or SDK language.

Interfaces are supported across all Dagger SDKs. The examples below focus on Go, Python, and TypeScript because they use language-native interface/protocol syntax when authoring modules. When a module exposes an interface, every SDK can consume that interface in generated clients and pass compatible objects to interface-typed arguments.

Declaration​

Declare an interface and use it as a function argument, return value, or object field. Dagger checks interfaces structurally: any object with compatible functions satisfies the interface, including objects from dependencies and objects written with another SDK. Interfaces can also satisfy other interfaces when their function sets are compatible.

Define a Go interface that embeds DaggerObject and use it in your Dagger Function signature.

Here is an example of the definition of an interface Fooer with a single function foo:

package main

import (
"context"
"dagger/my-module/internal/dagger"
)

type MyModule struct{}

type Fooer interface {
DaggerObject
Foo(ctx context.Context, bar int) (string, error)
}

func (m *MyModule) Foo(ctx context.Context, fooer Fooer) (string, error) {
return fooer.Foo(ctx, 42)
}

Functions defined in interface definitions must match the client-side API signature style. If they return a scalar value or an array, they must accept a context.Context argument and return an error return value. If they return a chainable object value, they must not return an error value, and they do not need to include a context.Context argument.

Note that you must also provide argument names, since they directly translate to the GraphQL field argument names.

Implementation​

No Dagger-specific implements marker is required. A Dagger object implements an interface when it exposes every required function with compatible argument and return types. You may still use language-native implements declarations where they are useful for editor or compiler checks.

Here is an example of a module Example that implements the Fooer interface:

package main

import (
"context"
"fmt"
)

type Example struct{}

func (m *Example) Foo(ctx context.Context, bar int) (string, error) {
return fmt.Sprintf("number is: %d", bar), nil
}

Usage​

Any object that implements the interface can be passed as an argument to the function that uses the interface.

Dagger automatically detects whether an object from the current module or one of its dependencies implements an interface defined in the current module or its dependencies. The generated SDK code then uses the idiomatic pattern for that language:

  • Go generates local As<Interface>() adapter methods on compatible objects. These are client-side type conversions only; they are not GraphQL schema fields and do not make network calls.
  • Python and TypeScript use structural typing, so compatible objects can be passed directly.

Here is an example of a module that uses the Example module defined above and passes it as argument to the foo function of the MyModule object:

package main

import (
"context"
)

type Usage struct{}

func (m *Usage) Test(ctx context.Context) (string, error) {
// Because `Example` implements `Fooer`, Go codegen generates a local
// adapter method. This is not a GraphQL call.
return dag.MyModule().Foo(ctx, dag.Example().AsMyModuleFooer())
}

Interface values and object IDs​

Interfaces are first-class GraphQL interfaces in the Dagger API. Implementing objects are listed in the schema, and Dagger can use GraphQL fragments when it needs to resolve the concrete type behind an interface value.

All object IDs now use the standard ID scalar. The schema uses @expectedType directives to preserve typed SDK signatures, and object loading uses the standard node(id:) field plus the built-in Node interface instead of per-type loadFooFromID fields. SDKs handle this internally in most cases, so module code can continue to pass typed SDK objects instead of raw IDs.

If you do need to work with raw IDs:

  • Use SDK helpers for loading typed objects where available. For example, Go clients can use dagger.Ref[T](client, id) for a lazy reference or dagger.Load[T](ctx, client, id) to verify that the ID resolves to T.
  • In raw GraphQL, load an object through node(id:) and select concrete fields with an inline fragment.

In Go, generated interface clients also include a Concrete(ctx) method that loads the underlying object as a Node, which can then be handled with a type switch when concrete-type-specific methods are needed.