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.
- Go
- Python
- TypeScript
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.
Define a Python protocol decorated with @dagger.interface and use it in your
Dagger Function signature.
Here is an example of the definition of an interface Fooer with a single
function foo:
import typing
import dagger
@dagger.interface
class Fooer(typing.Protocol):
@dagger.function
async def foo(self, bar: int) -> str: ...
@dagger.object_type
class MyModule:
@dagger.function
async def foo(self, fooer: Fooer) -> str:
return await fooer.foo(42)
Functions defined in interface definitions must match the client-side API
signature style. If they don't return a chainable object, they must be
coroutines (async).
Note that function arguments need to be properly named, since they directly translate to the GraphQL field argument names.
Define a TypeScript interface and use it in your Dagger Function signature.
Here is an example of the definition of an interface Fooer with a single
function foo:
import { func, object } from "@dagger.io/dagger"
export interface Fooer {
foo(bar: number): Promise<string>
}
@object()
export class MyModule {
@func()
async foo(fooer: Fooer): Promise<string> {
return await fooer.foo(42)
}
}
Functions defined in interface definitions must match the client-side API signature style:
- Always define asynchronous functions in interfaces by wrapping the return type
in
Promise<T>. - Declare the function as a method signature (for example,
foo(bar: number): Promise<string>) or a property signature (for example,foo: (bar: number) => Promise<string>). - Parameters must be properly named 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:
- Go
- Python
- TypeScript
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
}
import dagger
@dagger.object_type
class Example:
@dagger.function
def foo(self, bar: int) -> str:
return f"number is: {bar}"
import { func, object } from "@dagger.io/dagger"
export interface Fooer {
foo(bar: number): Promise<string>
}
@object()
export class Example implements Fooer {
@func()
async foo(bar: number): Promise<string> {
return `number is: ${bar}`
}
}
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:
- Go
- Python
- TypeScript
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())
}
import dagger
from dagger import dag
@dagger.object_type
class Usage:
@dagger.function
async def test(self) -> str:
return await dag.my_module().foo(dag.example())
import { dag, func, object } from "@dagger.io/dagger"
@object()
export class Usage {
@func()
async test(): Promise<string> {
return dag.myModule().foo(dag.example())
}
}
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 ordagger.Load[T](ctx, client, id)to verify that the ID resolves toT. - 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.