How to convert json to a github actions workflow yaml
- Step 1Shape the JSON as a workflow object — Top-level keys:
on(triggers),jobs(an object of job definitions), and optionallyenv. Each job needsruns-onand astepsarray of step objects. Generators usually emit exactly this shape. - Step 2Drop the JSON file on the tool — There's no paste field — drop your
.jsonfile. The free tier accepts up to 2 MB; Pro raises it to 100 MB, far more than any workflow needs. - Step 3Use indent 2 and run — Leave indent at 2 for standard workflow formatting, then click Convert to YAML. Indent 4 expands each step's
-onto its own line — valid, but not how.github/workflows/files normally read. - Step 4Expect 'on': to be quoted — that's fine — The output will show
'on':instead ofon:. This is correct and GitHub Actions accepts it, because YAML 1.1 treats bareonas a boolean. Do not 'fix' it by deleting the quotes — leaving it quoted is the safe form. - Step 5Eyeball expressions and reserved values —
${{ ... }}expressions are emitted unquoted, which GitHub accepts. Review any value you intend as a literal string that looks like a number or boolean (e.g. a version'3.10') — those should be quoted, and js-yaml quotes them automatically when they're string-typed in the JSON. - Step 6Commit to .github/workflows/ and check the Actions tab — Save as
.github/workflows/ci.yml, push, and open the Actions tab to confirm GitHub recognises the workflow and the triggers fire. To round-trip-verify, convert back with yaml-to-json and diff.
The two real conversion controls
These are the only options in the json-to-yaml UI. Line width (80) and anchor handling are fixed.
| Control | Values | Default | GitHub Actions impact |
|---|---|---|---|
| Indent | 2 or 4 | 2 | Use 2 for standard workflow style. Indent 4 expands each step's - to its own line — valid, but unusual for .github/workflows/ |
| Sort keys alphabetically | on / off | off (preserves order) | Off keeps on/env/jobs order. On alphabetises every mapping, so 'on' may sort after env/jobs — workflow still runs, but reads out of the conventional order |
| Line width (fixed) | 80 columns | 80 (not adjustable) | A long single-line run: command over 80 chars folds with >-. The command runs the same; if you need a true multi-line script, use a \n-containing string to get |- |
| Anchors / aliases (fixed) | disabled | noRefs = true | Repeated blocks are written out fully — no &anchor/*alias. GitHub Actions supports YAML anchors, but writing them out keeps the file unambiguous |
GitHub Actions value behaviour
How the converter handles the specific tokens that make GitHub Actions YAML tricky.
| JSON in | YAML out | GitHub note |
|---|---|---|
"on": { ... } (key) | 'on': (quoted key) | Quoted because YAML 1.1 reads `on` as boolean. GitHub Actions accepts 'on': — leave it quoted |
"${{ secrets.TOKEN }}" | ${{ secrets.TOKEN }} (unquoted) | Expression emitted unquoted; GitHub accepts it. Quote it yourself only if combined with literal text |
"node-version": "20" | node-version: '20' | String '20' stays a quoted string — correct, since setup-node wants a string version |
"runs-on": "ubuntu-latest" | runs-on: ubuntu-latest | Plain string, unquoted |
"matrix": {"os": [...], "node": [...]} | nested matrix: block | Fans out into the parallel job grid GitHub generates |
"needs": ["build", "test"] | needs: sequence | Dependency list as a YAML sequence |
Cookbook
Real workflow JSON converted to .yml. Secret names are illustrative; nothing is uploaded.
Why 'on': comes out quoted
ExampleThe first surprise everyone hits. js-yaml uses YAML 1.1, where on is a boolean keyword, so it quotes the on: key to keep it a string. GitHub Actions parses 'on': fine. Do not remove the quotes.
JSON:
{"on":{"push":{"branches":["main"]}},
"jobs":{"build":{"runs-on":"ubuntu-latest"}}}
YAML (indent 2):
'on':
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
# 'on': is valid and accepted by GitHub Actions.Steps with a with: input block
ExampleA step array converts to a YAML sequence; each step's with: map nests beneath it. The expression in with: stays unquoted, which GitHub accepts.
JSON:
{"steps":[
{"uses":"actions/checkout@v4"},
{"uses":"actions/setup-node@v4",
"with":{"node-version":"20"}}
]}
YAML:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'Matrix strategy fan-out
ExampleA strategy.matrix object with two dimensions converts to the YAML grid GitHub uses to generate parallel runs (here 2 OSes × 2 Node versions = 4 jobs).
JSON:
{"strategy":{"matrix":{"os":["ubuntu-latest","windows-latest"],
"node":[18,20]}}}
YAML (indent 2):
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
node:
- 18
- 20Expressions and a multi-line run script
Example${{ secrets.TOKEN }} stays unquoted. A run value with real newlines becomes a literal |- block — the readable multi-line shell form. (A single long line without newlines would fold with >- instead.)
JSON:
{"env":{"TOKEN":"${{ secrets.TOKEN }}"},
"steps":[{"run":"npm ci\nnpm test\nnpm run build"}]}
YAML:
env:
TOKEN: ${{ secrets.TOKEN }}
steps:
- run: |-
npm ci
npm test
npm run buildneeds: dependency graph
ExampleA needs array becomes a YAML sequence, defining the job dependency order GitHub enforces.
JSON:
{"jobs":{"deploy":{"needs":["build","test"],
"runs-on":"ubuntu-latest"}}}
YAML:
jobs:
deploy:
needs:
- build
- test
runs-on: ubuntu-latestErrors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
on: trigger key emitted as 'on':
Preservedjs-yaml uses YAML 1.1, where on is a boolean keyword, so the on: key is quoted to 'on': to keep it a string. GitHub Actions accepts 'on': and runs the workflow normally. Do not strip the quotes — bare on: would be reinterpreted by strict YAML 1.1 parsers as the boolean true.
Expressions emitted unquoted
Preserved${{ secrets.TOKEN }} and ${{ matrix.os }} are written without quotes, which GitHub Actions accepts. The only case you must hand-quote is when an expression is embedded in literal text that would otherwise be ambiguous (e.g. starting a value with ${{); for standalone expressions, unquoted is fine.
Version strings quoted to stay strings
PreservedA JSON string like "3.10" (a Python version) becomes '3.10' quoted, preventing YAML from reading it as the number 3.1. This is correct — setup-python/setup-node expect string versions. If your generator emitted 3.10 as a JSON number, it already lost the trailing zero before conversion.
Indent 4 expands step sequences
ExpectedAt indent 4, each step's - is written on its own line with the step body indented beneath. GitHub Actions parses it, but it's not how workflows normally read and may confuse reviewers. Use indent 2.
Invalid JSON input
ErrorThe converter runs JSON.parse. A trailing comma, a comment, or JS-style unquoted keys throw a parse error and nothing converts. If your generator produced near-JSON or you hand-edited it, repair it with json-format-fixer first.
Sorting keys reorders the workflow
ExpectedTurning on sort-keys alphabetises every mapping, so 'on' (quoted) may sort after env and jobs. The workflow still runs, but the conventional on → env → jobs reading order is lost. Leave sort-keys off for workflows unless you specifically want diff-stable ordering.
Multi-line run becomes |-, long single line folds
SupportedA run string with embedded newlines (\n) becomes a readable literal block (|-). A single long line over 80 chars folds with >- instead. Both execute identically; if you want the readable block form, keep your script as multi-line JSON.
Anchors not generated for repeated blocks
By designRepeated step or job fragments are written out in full — js-yaml's anchor generation is disabled (noRefs fixed on). GitHub Actions supports YAML anchors, but writing blocks out keeps the workflow self-contained and unambiguous in code review.
Empty maps and arrays
PreservedAn empty JSON object becomes {} and an empty array becomes [] (flow style) on a single line. For example an empty with: {} is valid; if a step genuinely needs no inputs, you can delete the with: line after conversion.
Frequently asked questions
Why is my on: trigger written as 'on': with quotes?
Because js-yaml uses YAML 1.1, where bare on is a boolean keyword. To keep it a string, the converter quotes the key as 'on':. GitHub Actions accepts 'on': and runs the workflow normally — this is valid and common. Don't remove the quotes; bare on: risks being reinterpreted as the boolean true by strict parsers.
Why does GitHub Actions sometimes reject YAML that looks correct?
Usually a YAML 1.1 keyword issue. Reserved words like on, yes, no, off must be quoted to stay strings — which the converter does automatically for the on key and for string-valued keywords. Also, values that look numeric but must be strings (a '3.10' version) need quotes, and the converter adds them when the JSON value is a string. If something is still rejected, check whether a value that should be a string was emitted as a number.
Are ${{ secrets.TOKEN }} expressions quoted?
No, they're emitted unquoted, which GitHub Actions accepts. You only need to quote an expression manually when it's combined with literal text that would make the line ambiguous. Standalone ${{ ... }} values are fine unquoted.
How do I represent a multi-dimension matrix?
Structure it in JSON as { "strategy": { "matrix": { "os": ["ubuntu-latest","windows-latest"], "node": [18,20] } } }. The converter produces the nested matrix: block GitHub uses to fan out parallel jobs (here 4 combinations). include/exclude arrays convert the same way.
Which indent should I use?
Indent 2 for standard workflow formatting. Indent 4 is valid but expands each step's - onto its own line, which is not how .github/workflows/ files normally look and can confuse reviewers.
Are secret names and token references sent to JAD Apps?
No. Conversion runs entirely in your browser using js-yaml. Workflow content — including ${{ secrets.* }} references, environment variable names, and GITHUB_TOKEN usage — never reaches a JAD Apps server. The only server-side record when signed in is an anonymous run counter.
How do I get a readable multi-line run script?
Put real newlines in the JSON run value ("npm ci\nnpm test"). js-yaml emits multi-line strings as a literal block (|-), which is the readable shell form. A single long line without newlines folds with >- instead.
What happens to an empty with: block?
An empty JSON object converts to with: {} (flow style on one line). It's valid, but if a step needs no inputs you can simply delete the with: line after conversion.
Should I turn on sort-keys for workflows?
Generally no. Sort-keys alphabetises every mapping, which can push the quoted 'on' key below env and jobs, losing the conventional reading order. Leave it off unless you specifically want byte-stable diffs between two generated workflows.
What's the file size limit?
Free tier accepts files up to 2 MB; Pro raises it to 100 MB — both vastly more than any workflow needs. The tool takes a dropped .json file; there's no paste box.
Can I verify the converted workflow matches the source?
Yes. Convert the .yml back to JSON with yaml-to-json and compare with json-diff. A clean diff (ignoring key order if you used sort-keys) confirms a faithful round-trip before you commit.
Does it generate anchors for repeated jobs or steps?
No. Anchor/alias generation is disabled, so repeated fragments are written out in full. GitHub Actions does support anchors, but writing blocks out keeps the workflow self-contained and easier to review.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.