What's changed: New GH-200 Chapter 3 (creating custom actions = JavaScript/Docker/composite types and selection, action.yml metadata, inputs/outputs, workflow commands, troubleshooting; distribution & maintenance = public/private/Marketplace, Marketplace publishing, tags/releases/moving major tag/SHA pinning, avoiding @main, immutable actions)
3.1Creating Custom Actions — Types, Metadata, and I/O
Understand the three action types (JavaScript, Docker container, composite) and how to choose, the required files and metadata in action.yml (inputs, outputs, runs, branding), using workflow commands within actions, and troubleshooting action execution errors.
An action is a reusable unit of code invoked from a workflow step via uses:. Beyond using off-the-shelf Marketplace actions, you can build and distribute your own logic as a custom action. There are three types, chosen by requirement: a JavaScript action (written in JavaScript/TypeScript), a Docker container action that encapsulates any language/tool in a Docker container, and a composite action that bundles multiple steps (introduced in Chapter 2).
3.1.1The three types and how to choose
A JavaScript action runs on Node.js, starts fast, and works on Linux/Windows/macOS runners (cross-platform); ship it with dependencies bundled. A Docker container action can include specific OS packages or language runtimes for full environment control, but is Linux-runner only and tends to start slower due to image build/pull. A composite action bundles existing steps (run: or other actions’ uses:) in YAML, ideal for shell-centric procedures. Summarize: "fast and cross-platform = JavaScript," "needs special deps/language = Docker," "aggregate existing steps = composite."
| Type | Runtime | Platforms | Best for |
|---|---|---|---|
| JavaScript | Node.js | Linux/Windows/macOS | Fast, cross-platform |
| Docker container | Container image | Linux only | Bundle special deps/language |
| Composite | Bundle of steps | Depends on runner | Aggregate existing steps |
3.1.2action.yml — metadata and I/O
For any type, the action root must contain an action.yml (or action.yaml) metadata file. It defines name, description, the inputs it accepts (type, required, default, description), the outputs it returns, and runs describing how it executes. runs.using matches the type: JavaScript uses node24 (node20 reaches EOL in April 2026; runners default to node24 from June 2026) + main:, Docker uses docker + image:, composite uses composite + steps:. branding sets a Marketplace icon/color. Inputs are passed from the workflow’s with: and read inside the action via the INPUT_<NAME> env var (or @actions/core getInput in JavaScript).
3.1.3Workflow commands within actions
Actions interact with the runner via workflow commands: set outputs with echo "name=value" >> "$GITHUB_OUTPUT", pass env vars forward with $GITHUB_ENV, emit log annotations ::notice::/::warning::/::error::, mask secrets with ::add-mask::, and fold logs with ::group::/::endgroup::. JavaScript actions perform the same via @actions/core (setOutput, setFailed, info, setSecret). This lets actions correctly report results (outputs) and state to the workflow.
3.1.4Troubleshooting actions
When an action fails, first check action.yml consistency (runs.using matching the implementation; inputs/outputs names align). For JavaScript actions, a classic issue is missing bundled dependencies (publishing without node_modules causes Cannot find module); bundle into a single file with tools like @vercel/ncc. For Docker actions, suspect image build failure, permissions, or the entrypoint. In all cases, use ::error:: annotations and (if needed) debug logging to pinpoint the cause.
Common: (1) JavaScript action = fast, cross-platform. Docker container action = can bundle special deps but is Linux-only and slower to start. Composite = bundle of existing steps. (2) Required is action.yml (name/description/inputs/outputs/runs.using = node24|docker|composite). (3) Inputs come from with: → INPUT_<NAME>/getInput; outputs via $GITHUB_OUTPUT/setOutput. (4) JS actions must bundle dependencies (ncc).
Watch out: (1) Docker container actions are Linux-only—not usable on Windows/macOS. (2) JavaScript actions fail with Cannot find module unless node_modules is bundled. (3) Composite actions (an action used as one step) and reusable workflows (calling a whole workflow) are different (Chapter 2). (4) runs.using (node24 / docker / composite) must match the implementation or it won’t start.
3.1.5Section summary
- Types = JavaScript (fast, cross-platform) / Docker (special deps, Linux-only) / composite (bundle of steps)
- action.yml is required: name/description/inputs/outputs/runs.using (node24|docker|composite)
- Inputs via with: → INPUT_<NAME>/getInput; outputs via $GITHUB_OUTPUT/setOutput; report state via workflow commands
- Bundle deps for JS (ncc); for Docker check build/permissions/entrypoint
Sign in to track progress — Log in.
Quick check
(just a quick review)Q1. You want your custom action to run on Linux, Windows, and macOS runners and start fast. Which type is best?
Q2. You build an action that must bundle specific system packages or a custom language runtime for full environment control. Which type and constraint apply?
Q3. Which file must sit at the custom action’s root and define name, inputs, outputs, and runs?
Q4. After publishing a JavaScript action, users hit frequent "Cannot find module" errors. What is the most likely cause and fix?
Q5. How do you make an action’s result (e.g., a generated tag) available to later steps in the calling workflow?
Q6. How do you read an input passed via the workflow’s with: inside a composite/shell action?

