When the same setup or logic repeats in many workflows, an action gives it a name, inputs and a version.
An action is a directory (or repository) with an action.yml describing its name, inputs, outputs and how it runs. There are three kinds:
- Composite:
runs.using: compositeand a list of steps, the same syntax as a job's steps. Eachrunstep must declare itsshell. Simplest option; good for bundling setup commands. - JavaScript:
runs.using: node24with amainfile. It runs directly on the runner, starts fast and works on all operating systems. Use the@actions/coretoolkit for inputs, outputs and logging, and commit a bundleddist/since the runner does not install dependencies. - Docker container:
runs.using: dockerwith a Dockerfile or image. Any language, but Linux runners only and slower to start.
Inputs reach composite steps as ${{ inputs.name }}, and JavaScript/Docker actions as INPUT_<NAME> environment variables. Outputs work like step outputs: write to $GITHUB_OUTPUT and map them in action.yml.
Store actions used by one repository in .github/actions/<name>/ and reference them as ./.github/actions/<name> after checkout. Publish shared actions in their own repository with semantic version tags and a moving major tag (v1) so users get fixes automatically.
Example
# .github/actions/setup-app/action.yml
name: Setup app
description: Install toolchain and dependencies
inputs:
node-version:
description: Node.js version
default: "22"
outputs:
cache-hit:
description: Whether dependencies came from cache
value: ${{ steps.setup.outputs.cache-hit }}
runs:
using: composite
steps:
- id: setup
uses: actions/setup-node@v7
with: {node-version: "${{ inputs.node-version }}", cache: npm}
- run: npm ci
shell: bash
# usage in a workflow, after actions/checkout:
# - uses: ./.github/actions/setup-app
# with: {node-version: "24"}Remember
- action.yml defines inputs, outputs and runs
- Composite actions bundle steps; each run step needs shell:
- JavaScript actions are fast and cross-platform; Docker actions are Linux-only
Watch out for
- Forgetting to commit the bundled dist/ of a JavaScript action; node_modules are never installed on use.