Runway lookups and CFNgin lookups are not interchangeable. While they do share a similar base class and syntax, they exist in two different registries. Runway config files can’t use CFNgin lookups just as the CFNgin config cannot use Runway lookups.

Runway’s CFNgin provides the ability to dynamically replace values in the config via a concept called lookups. A lookup is meant to take a value and convert it by calling out to another service or system.

A lookup is denoted in the config with the ${<lookup type> <lookup input>} syntax.

Lookups are only resolved within Variables. They can be nested in any part of a YAML data structure and within another lookup itself.


If a lookup has a non-string return value, it can be the only lookup within a field.

e.g. if custom returns a list, this would raise an exception:

Variable: ${custom something}, ${output otherStack.Output}

This is valid:

Variable: ${custom something}

For example, given the following:

  - name: sg
    class_path: some.stack.blueprint.Blueprint
        - ${output otherStack.IAMRole}
          Custom: ${custom ${output otherStack.Output}}
          DBUrl: postgres://${output dbStack.User}@${output dbStack.HostName}

The Blueprint would have access to the following resolved variables dictionary:

    "Roles": ["other-stack-iam-role"],
    "Values": {
        "Env": {
            "Custom": "custom-output",
            "DBUrl": "postgres://user@hostname",

Built-in Lookups

Writing A Custom Lookup

A custom lookup may be registered within the config. It custom lookup must be in an executable, importable python package or standalone file. The lookup must be importable using your current sys.path. This takes into account the sys_path defined in the config file as well as any paths of package_sources.

The lookup must be a subclass of LookupHandler with a @classmethod of handle with a similar signature to what is provided in the example below. The subclass must override the TYPE_NAME class variable with a name that will be used to register the lookup. There must be only one lookup per file.

The lookup must return a string if being used for a CloudFormation parameter.

If using boto3 in a lookup, use context.get_session() instead of creating a new session to ensure the correct credentials are used.


When using a pydantic.root_validator() or pydantic.validator() in a lookup allow_reuse=True must be passed to the decorator. This is because of how lookups are loaded/re-loaded when they are registered. Failure to do so will result in an error if the lookup is registered more than once.


"""Example lookup."""
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Final, Literal, Optional, Union

from runway.cfngin.utils import read_value_from_path
from runway.lookups.handlers.base import LookupHandler

    from runway.cfngin.providers.aws.default import Provider
    from runway.context import CfnginContext, RunwayContext

class MylookupLookup(LookupHandler):
    """My lookup."""

    TYPE_NAME: Final[Literal["mylookup"]] = "mylookup"
    """Name that the Lookup is registered as."""

    def handle(
        value: str,
        context: Union[CfnginContext, RunwayContext],
        *_args: Any,
        provider: Optional[Provider] = None,
        **_kwargs: Any
    ) -> str:
        """Do something.

            value: Value to resolve.
            context: The current context object.
            provider: CFNgin AWS provider.

        query, args = cls.parse(read_value_from_path(value))

        # example of using get_session for a boto3 session
        s3_client = context.get_session().client("s3")

        return "something"