d2lang/d2 · error

no layout resolver configured for layout engine %q

Error message

no layout resolver configured for layout engine %q

What it means

d2lib's getLayout resolves the layout engine name through an optional LayoutResolver function in CompileOptions. If a Layout name was provided but no LayoutResolver was configured, there is no way to map the name to an implementation, so compilation fails with this error.

Source

Thrown at d2lib/d2.go:199

	}
	previous := geometry
	for i, l := range g.Steps {
		ld, stepGeometry, err := compileBoard(ctx, l, compileOpts, renderOpts, previous, i+1 < len(g.Steps))
		if err != nil {
			return nil, nil, err
		}
		d.Steps = append(d.Steps, ld)
		previous = stepGeometry
	}
	return d, geometry, nil
}

func getLayout(opts *CompileOptions) (d2graph.LayoutGraph, error) {
	if opts.Layout == nil {
		return nil, errors.New("no available layout")
	}
	if opts.LayoutResolver == nil {
		return nil, fmt.Errorf("no layout resolver configured for layout engine %q", *opts.Layout)
	}
	return opts.LayoutResolver(*opts.Layout)
}

func getEdgeRouter(opts *CompileOptions) (d2graph.RouteEdges, error) {
	if opts.Layout != nil && opts.RouterResolver != nil {
		router, err := opts.RouterResolver(*opts.Layout)
		if err != nil {
			return nil, err
		}
		if router != nil {
			return router, nil
		}
	}
	return d2layouts.DefaultRouter, nil
}

// applyConfigs applies the configs read from D2 and applies it to passed in opts

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Set CompileOptions.LayoutResolver to a resolver such as d2plugin.NativeResolver or your custom mapping function
  2. Omit Layout to use the default path instead of naming an engine you cannot resolve
  3. Use the higher-level d2lib helpers that wire the resolver automatically

Example fix

// before
opts := &d2lib.CompileOptions{Layout: &layoutName}
// after
opts := &d2lib.CompileOptions{
	Layout: &layoutName,
	LayoutResolver: d2plugin.NativeResolver,
}
Defensive patterns

Strategy: validation

Validate before calling

if opts.Layout != nil && opts.LayoutResolver == nil {
	return errors.New("Layout set but LayoutResolver is nil")
}

Type guard

func optionsReady(opts *d2lib.CompileOptions) bool {
	return opts.Layout == nil || opts.LayoutResolver != nil
}

Try / catch

if _, err := d2lib.Compile(ctx, input, opts); err != nil {
	if strings.Contains(err.Error(), "no layout resolver configured") {
		opts.LayoutResolver = d2plugin.NativeResolver
		_, err = d2lib.Compile(ctx, input, opts)
	}
	return err
}

Prevention

When it happens

Trigger: Calling d2lib.Compile with CompileOptions.Layout set to a non-nil engine name (e.g. "dagre" or "elk") while leaving CompileOptions.LayoutResolver nil.

Common situations: Custom compile setups in embedding code (plugins, LSP tools) that set Layout but forget the resolver; tests constructing CompileOptions manually; copying options structs partially between call sites.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/dba0eadb33753b4d. Report an issue: GitHub.