5.1 Action Architecture & action.yml Specification
Key Takeaways
The action.yml (or action.yaml) metadata manifest is the mandatory declarative contract defining an action's name, description, inputs, outputs, branding, and execution runtime (runs.using).
GitHub Actions supports three distinct action execution models: JavaScript actions (Node.js runtime), Docker container actions (Linux container runtime), and Composite actions (multi-step shell and action pipelines).
Action inputs support validation keys including description, required, default, and deprecationMessage, while outputs define values mapped from execution steps or runtime code.
Actions can be published in dedicated public or private repositories (referenced via owner/repo@ref) or stored in subdirectories of a workflow repository (referenced locally via uses: ./.github/actions/my-action).
The branding block configures visual metadata for GitHub Marketplace, specifying a Feather icon and background badge color.
Action Architecture & action.yml Specification
In GitHub Actions, Actions represent the smallest reusable unit of computational work within a workflow step. While workflows orchestrate jobs and execution graphs, actions encapsulate specialized logic—such as configuring a toolchain, compiling binaries, interacting with the GitHub REST API, scanning container images, or publishing deployment artifacts.
Authoring custom actions allows organizations to standardize CI/CD patterns, eliminate boilerplate code across repositories, encapsulate security policies, and share automated solutions across the broader GitHub ecosystem. On the GitHub Actions Certification (GH-200) exam, custom action authoring accounts for a substantial portion of Domain 3 ("Author and Maintain Actions"). Candidates must demonstrate deep mastery of metadata schemas, runtime engine selection, and referencing syntax.
1. The Action Manifest: action.yml
Every custom action requires a metadata manifest file named either action.yml or action.yaml placed in the root of the action's repository or within a dedicated subfolder. This file is written in YAML syntax and defines the contract between the action author and the workflow caller.
+-----------------------------------------------------------------------------+
| ACTION.YML MANIFEST ARCHITECTURE |
| |
| +---------------------------------------------------------------------+ |
| | METADATA IDENTIFIERS | |
| | - name: 'Enterprise Security Linter' | |
| | - author: 'DevOps Security Team' | |
| | - description: 'Audits source code for credential leaks and CVEs' | |
| +---------------------------------------------------------------------+ |
| | |
| v |
| +----------------------------------+----------------------------------+ |
| | | | |
| v v v |
| +--------------------+ +--------------------+ +--------------------+ |
| | INPUTS DEFINITION | | OUTPUTS DEFINITION | | BRANDING DEFINITION| |
| | - target-path | | - scan-status | | - icon: 'shield' | |
| | - fail-on-critical | | - vulnerabilities | | - color: 'red' | |
| +--------------------+ +--------------------+ +--------------------+ |
| | |
| v |
| +---------------------------------------------------------------------+ |
| | EXECUTION ENGINE: runs.using | |
| | - 'composite' (Combines run / uses steps) | |
| | - 'node20' (Executes JavaScript bundle on Node 20 runtime) | |
| | - 'docker' (Builds or pulls Linux container image) | |
| +---------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------+
Top-Level Manifest Keys
| Key | Required | Type | Description & Purpose |
|---|---|---|---|
name | Yes | String | The human-readable name of the action displayed in workflow logs and the GitHub Marketplace. |
author | No | String | The name of the individual developer or organization maintaining the action. |
description | Yes | String | A detailed summary explaining what the action does, its prerequisites, and its operational behavior. |
inputs | No | Object | Defines the input parameters accepted by the action, including data types, defaults, and requirements. |
outputs | No | Object | Defines the output variables produced by the action and exposed to subsequent workflow steps. |
runs | Yes | Object | Configures the underlying execution engine, runtime environment, entrypoint scripts, and lifecycle hooks. |
branding | No | Object | Customizes the visual badge icon and background color when published to the GitHub Marketplace. |
2. Deep Dive: inputs and outputs Schema
Parameterizing with inputs
The inputs: map defines parameters callers can pass via the with: keyword in a workflow step. Each input key supports four configuration parameters:
inputs:
api-token:
description: 'Authentication token for API communication'
required: true
scan-mode:
description: 'Scanning mode verbosity'
required: false
default: 'standard'
legacy-flag:
description: 'Deprecated flag replaced by scan-mode'
required: false
deprecationMessage: 'The legacy-flag input is deprecated. Use scan-mode instead.'
description(Required): Clear explanation of the input's purpose and expected values.required(Optional, Boolean): Iftrue, the caller workflow step must provide this input; if omitted, GitHub Actions aborts the job before execution with a validation error. Defaults tofalse.default(Optional, String): The fallback value injected if the caller does not supply this input. Note that all defaults inaction.ymlare interpreted as strings.deprecationMessage(Optional, String): When a workflow supplies this input, the runner logs a warning annotation containing this message, informing developers to migrate to a newer parameter.
Declaring outputs
The outputs: block registers data generated during execution for use downstream:
outputs:
scan-passed:
description: 'Boolean flag indicating whether the security scan succeeded'
value: ${{ steps.audit-step.outputs.passed }} # Mandatory for composite actions
findings-count:
description: 'Total number of detected vulnerabilities'
value: ${{ steps.audit-step.outputs.total_cves }}
Important
For Composite Actions, the value: parameter is mandatory for every output in action.yml. It binds the action's public output key to a specific internal step's output (${{ steps.<step_id>.outputs.<output_key> }}). For JavaScript and Docker actions, outputs are registered dynamically at runtime via the core SDK or $GITHUB_OUTPUT.
3. The branding Block for GitHub Marketplace
If you publish an action to the GitHub Marketplace, the branding block defines the visual badge rendered on the listing:
branding:
icon: 'shield'
color: 'purple'
icon: Must match an official Feather icon name (e.g.,activity,alert-circle,archive,award,box,check-circle,cloud,code,cpu,database,download,file-text,git-branch,globe,layers,lock,package,play,server,shield,terminal,tool,upload-cloud,zap).color: Restricted to standard Marketplace badge colors:white,yellow,blue,green,orange,red,purple, orgray-dark.
4. Comparing the Three Action Types
GitHub Actions supports three distinct runtime paradigms under runs.using:
+-----------------------------------------------------------------------------+
| COMPARISON OF ACTION EXECUTION TYPES |
| |
| +-----------------------+-----------------------+---------------------+ |
| | JAVASCRIPT ACTION | COMPOSITE ACTION | DOCKER ACTION | |
| +-----------------------+-----------------------+---------------------+ |
| | - runs.using: 'node20'| - runs.using: 'comp..'| - runs.using: 'doc..'| |
| | - Native Node runtime | - Multiple run/uses | - Linux container | |
| | - Linux, macOS, Win | - Linux, macOS, Win | - Linux ONLY | |
| | - Sub-second startup | - Sub-second startup | - Container pull/bld| |
| | - Bundled dist/index | - Shell scripts | - System tooling | |
| +-----------------------+-----------------------+---------------------+ |
+-----------------------------------------------------------------------------+
Comprehensive Action Types Comparison Matrix
| Characteristic | JavaScript Actions | Composite Actions | Docker Container Actions |
|---|---|---|---|
runs.using Identifier | 'node20' | 'composite' | 'docker' |
| Operating System Support | Linux, macOS, Windows | Linux, macOS, Windows | Linux ONLY (Fails on macOS/Windows) |
| Startup Overhead | Extremely Low (< 1 second) | Extremely Low (< 1 second) | High (Image pull or local docker build) |
| Execution Speed | Very Fast (Direct Node.js execution) | Fast (Executes native shell processes) | Moderate (Container namespace isolation) |
| Runtime Environment | Node.js 20 bundled on runner | Host runner shell (bash, pwsh, etc.) | Custom Linux image (Dockerfile / registry) |
| Dependency Management | Pre-bundled via @vercel/ncc | Uses tools pre-installed on runner | Packaged inside the container filesystem |
| Nested Actions Support | No (Uses Octokit / Node libraries) | Yes (Can execute uses: steps) | No (Executes container entrypoint) |
Lifecycle Hooks (pre/post) | Yes (pre, main, post) | No | Yes (pre-entrypoint, post-entrypoint) |
| Primary Use Cases | API integrations, PR bots, fast logic | Multi-step workflow scripts, reusable setups | Heavy CLI toolchains, C/Rust, custom OS packages |
5. Referencing Custom Actions in Workflows
Workflows invoke custom actions using the uses: keyword. Actions can be consumed from external repositories or from within the local repository.
Referencing Remote Actions
steps:
# Pinned to a major release tag
- name: Run Security Audit
uses: enterprise-org/security-linter@v2
with:
scan-mode: 'deep'
# Pinned to an immutable commit SHA (Recommended for security)
- name: Run Build Action
uses: enterprise-org/builder-action@8a7b9c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b
# Stored in a repository subdirectory
- name: Run Sub-Action
uses: enterprise-org/monorepo/actions/setup-db@v1
Referencing Local Actions
When an action is authored inside the same repository as the workflow (often in .github/actions/), use relative directory paths starting with ./:
jobs:
ci:
runs-on: ubuntu-latest
steps:
# 1. You MUST check out the repository first so local files exist on the runner
- name: Checkout Repository Code
uses: actions/checkout@v4
# 2. Invoke local action using relative path
- name: Execute In-Tree Linter
uses: ./.github/actions/custom-linter
with:
target-path: './src'
Caution
The Local Action Checkout Requirement:
When using uses: ./.github/actions/my-action, the runner looks for the action definition on the runner's local filesystem at $GITHUB_WORKSPACE/.github/actions/my-action. If you do not run actions/checkout before calling a local action, the directory does not exist, and the step fails immediately with Action directory does not exist.
An action maintainer needs to deprecate an optional input named auth-token in favor of a new input named api-key. How should the action.yml manifest be structured so that workflows passing auth-token receive a warning annotation in the workflow logs without breaking the build?
Add deprecationMessage: 'The auth-token input is deprecated. Use api-key instead.' under the auth-token input definition in action.yml.
Remove auth-token from inputs in action.yml and catch the missing key in the action's JavaScript entrypoint.
Set required: false and default: 'deprecated' under auth-token, then throw an unhandled exception.
Add deprecated: true to the top-level manifest metadata next to name and description.
A DevOps team is developing a custom action that must execute on Linux, macOS, and Windows GitHub-hosted runners with minimal startup latency (< 1 second). The action executes complex business logic, calls the GitHub REST API, and parses JSON payloads. Which action type should the team select?
A Docker container action using an Alpine Linux base image.
A Composite action executing multi-line Bash scripts with Docker CLI commands.
A JavaScript action using runs.using: 'node20' bundled with @vercel/ncc.
A Docker container action referencing a prebuilt image on GitHub Packages.
A workflow file contains a step invoking a local custom action: uses: ./.github/actions/setup-toolchain. When the workflow runs, the step fails immediately with an error indicating that the action path cannot be found. What is the most likely cause of this failure?
Local actions can only be referenced from .github/workflows/actions/ rather than .github/actions/.
The workflow failed to execute actions/checkout prior to the local action step, meaning the repository files were not present on the runner filesystem.
Local actions must always be packaged as Docker containers and cannot use composite or node20.
The uses: keyword cannot reference relative file paths and requires a full repository URL.
Sections you finish are checked off in the contents.