Writing custom actions

GitHub Actions · Advanced · 14 min

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: composite and a list of steps, the same syntax as a job's steps. Each run step must declare its shell. Simplest option; good for bundling setup commands.
  • JavaScript: runs.using: node24 with a main file. It runs directly on the runner, starts fast and works on all operating systems. Use the @actions/core toolkit for inputs, outputs and logging, and commit a bundled dist/ since the runner does not install dependencies.
  • Docker container: runs.using: docker with 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.

Check yourself

  1. Which action type cannot run on a macOS runner?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.github.com/en/actions/sharing-automations/creating-actions/about-custom-actions

Writing custom actions · GitHub Actions · Topolearn