JavaScript Actions

Writing a Custom JavaScript Action

A JavaScript action runs a Node.js 2,131 program directly on the runner, with no shell or container, which makes it the fastest kind and the only kind that runs unchanged on Linux, Windows and macOS. Its action.yml has the same name, description, inputs (genre) and outputs (count) keys as the composite one, plus runs: {using: node24, main: index.js} and optional branding. The program:

.github/actions/count-books/index.jsJavaScript
// A dependency-free JavaScript action: inputs arrive as INPUT_<NAME> environment variables.
const fs = require('node:fs');
const genre = process.env.INPUT_GENRE;
const { books } = JSON.parse(fs.readFileSync('db/seed.json', 'utf8'));
const count = books.filter((book) => book.genre === genre).length;
fs.appendFileSync(process.env.GITHUB_OUTPUT, `count=${count}\n`);
console.log(`::notice title=count-books::${count} ${genre} book(s) in the seed data`);

The runner passes each input as an INPUT_<NAME> variable and reads outputs from $GITHUB_OUTPUT (Runners and Commands), so the action needs no packages; it printed the ##[notice] line in 3.12.2. Larger actions use the actions/toolkit (https://github.com/actions/toolkit 5,859 ) packages (npm 2,036 install @actions/core @actions/github) and bundle everything into one file with @vercel/ncc, because the runner never runs npm install for an action. Node.js 24 has been the runners' default since 16 June 2026, and node20 was due for removal on 23 September 2026. A Docker 514 container action (using: docker) runs any language, on Linux runners only.