# Using Composite Actions with GitHub Actions

> Composite Actions let one GitHub Action run several shell steps in bash, Python, Node or PowerShell. How to write one, pass inputs and outputs, and call it.

- URL: https://doug.sh/posts/using-composite-actions-with-github-actions/
- Author: Doug Calobrisi
- Date: 2021-09-11
- Updated: 2026-09-14
- Tags: github, actions


## Basics

When developing new automations it's best to make each component as modular and reusable as possible. When using GitHub Actions, Composite Actions may be your answer for quick development and reusability. 

Composite Actions allow you to execute multiple shell steps by calling the Action. These shell steps can be bash, python, nodejs, or powershell. This functionality can be very powerful when you have logic you often want to reuse.

Take for example, running a small Python script that requires a dependency in an Actions Workflow. You could create a Composite Action that:

- Using the bash shell, executes `pip3 install <package>`
- Using the python shell, include a Python script in-line
- OR, using the bash shell, execute a Python script in a file

That might look something like:

```yaml
name: 'run-python-script'
description: 'Installs a package and runs a python script'
runs:
  using: "composite"
  steps:
    - name: Install boto3
      shell: bash
      run: pip3 install boto3

    - name: Run Python Script
      shell: python
      run: |
        print("hello world")
      
```

It looks much like a Workflow YAML file but is a little different. The important part to note is `using: composite`. Additionally, only the `run` command can be used to execute code in a specified `shell`.

> There is one huge caveat for using Composite Actions - you can't call another Action.

## How and Where

With that concept in mind, you may want to create many Composite Actions. But, how and where do you put them?

Each GitHub Action must reside in its own `action.yml` file in its own directory. I'd suggest creating a repository just for reusable Actions. In that repo, or your primary repo, create an `/actions/` directory to store the Actions. Put each Action in a separate directory.

It should look like this:

```
actions
├── another-action
│   └── action.yml
└── first-action
    └── action.yml
```

## Inputs and Outputs

Just like a function or method in programming languages, Composite Actions provide for inputs (parameters) or outputs (return values). 

In this example, we have an input parameter named `name` and an output called `greeting`. Using the `bash` shell, we return `Hello <name>` as the greeting output.

```yaml
name: 'say-hello'
description: 'Returns a greeting output with a greeting to the input name'
inputs:
  name:
    required: true
outputs:
  greeting:
    required: true
runs:
  using: "composite"
  steps:
    - name: say-hello
      shell: bash
      env:
        INPUT_NAME: ${{ inputs.name }}
      run: |
        GREETING="Hello $INPUT_NAME"
        echo "::set-output name=greeting::$GREETING"
```

We need to do a few things to use inputs and outputs. 

First, the `inputs` and `outputs` stanzas must be included in the YAML. 

Then, to get the input values as environment variables, we must specify under the `env` key. This is because the inputs are not automatically provided as environment variables when using Composite Actions, unlike Workflows.

Finally, to use the output, we must print a special string to stdout. The string must be in the format of `::set-output name=<NAME>::<VALUE>`. This can be accomplished using `print()` in Python, `console.log()` in JavaScript, or `echo` in bash. 

## Calling Composite Actions

You can call a Composite Action from a Workflow just like any other Action. If you're storing them in a private repository, you need to check the repository out first.

After the Composite Action has run, the outputs of the Action are available as `steps.<step-name>.outputs.<output-name>` and can be used in any future steps. 

In this example we're always using `Doug` as the value of `name`, but it could just as well be an input to the `workflow_dispatch` event.


```yaml
name: Greetings
on: [workflow_dispatch]
jobs:
  greeting:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout this repo
      uses: actions/checkout@v2

    - name: Call Composite Action say-hello
      uses: ./actions/say-hello
      id: say-hello
      with:
        name: 'Doug'

    - name: echo output from say-hello
      run: |
        echo ${{ steps.say-hello.outputs.greeting }}
```

