GitLab CI: Format and Validate Your .gitlab-ci.yml Online

GitLab CI: Format and Validate Your .gitlab-ci.yml Online

Stages, includes, `rules:`: structurally validate a .gitlab-ci.yml before the pipeline runs — not a GitLab runner, not official CI Lint.

29.08.2026
9 min read
Share this article:
GitLab CI
YAML
CI/CD
DevOps

Why check a .gitlab-ci.yml before the pipeline runs?

A .gitlab-ci.yml that does not parse, a stages value that is not an array, or a job with neither script nor trigger, often shows up too late: red badge, Pipeline Editor, blocked merge. FastMinify does not ship GitLab’s official CI Lint and does not run a runner. The GitLab CI validator runs structural checks in the browser — root mapping, stages, jobs with script or trigger, include, hidden jobs . — and the GitLab CI formatter reindents YAML (2 or 4 spaces). The full cluster lives on the CI/CD tools hub. To minify JS/CSS in the same pipeline, see the CI/CD minification guide.

Catch invalid YAML, a mistyped stages list, or a job without script/trigger before you push
Format 2- or 4-space indent for a readable review
100% in the browser — the file is never uploaded
512 KiB UTF-8 cap, same as other DevOps tools on the site
Leave GitLab CI Lint (remote includes, rules, official schema) to glab and the UI

GitLab vs GitHub Actions: two YAML shapes, three layers

One root file, not a workflows folder

GitHub Actions lives under .github/workflows/*.yml (many files, on trigger, jobs with steps). GitLab CI almost always lives in a single .gitlab-ci.yml at the repo root: stages, named jobs, include. FastMinify tools follow that split — do not paste an Actions workflow into the GitLab validator.

GitLab: one YAML document, jobs at the top level (except reserved keys)
GitHub: nested jobs map plus required on — see the GitHub Actions guide
Dockerfile and Compose stay on the DevOps hub
For generic YAML (not CI), use beautify-yaml
A minified JS artifact from a job is not CI YAML: unminify-js
Format, validate, CI Lint: three layers, not one button

FastMinify exposes two tools. Neither is GitLab CI Lint. Mixing the three up produces a file that looks “valid” here and still fails in Pipeline Editor.

Format: js-yaml round-trip, 2- or 4-space indent (default 2) — see format-gitlab-ci
Validate: YAML parse plus structure (mapping, stages, jobs, include) — see validate-gitlab-ci
GitLab CI Lint: official schema, include expansion, rules: evaluation — Pipeline Editor UI, API, or glab ci lint
YAML comments are dropped on format (same limit as beautify-yaml) — keep a copy if you rely on them
A “structure OK” verdict does not prove a remote include exists or that a rules: clause selects the job
What FastMinify does not simulate

The validator does not talk to a GitLab runner, does not call the CI Lint API, and does not expand remote includes. Editorial mentions of rules: are a real GitLab topic — not a browser feature.

No script execution, no Docker image, no runner cache
No include: remote / include: project expansion — only the pasted YAML is read
No evaluation of rules:, only/except, or workflow:rules
pages jobs are jobs like any other: they need script or trigger
Schema + includes + rules stay GitLab CI Lint, not FastMinify

Anatomy of a .gitlab-ci.yml: what FastMinify checks

The minimum shape of a GitLab CI config

The root must be a YAML mapping, not a scalar or a list. One document: repeated --- is rejected. If stages is present, it must be an array of non-empty names. Every key that is not reserved and does not start with . is a job: it needs script (non-empty string or list) or trigger.

Reserved keys (not jobs): stages, variables, include, workflow, default, image, services, cache, before_script, after_script, spec
Hidden / template jobs .hidden: no script requirement
Job with only extends: warning — a parent must provide script or trigger
No jobs and no top-level include: warning, not always a blocker
The results panel counts jobs, stages, and includes after a successful parse
Format: js-yaml round-trip, comments gone

The formatter pretty-prints via js-yaml. It is not a semantic rewriter: key order may change, # comments disappear, indent becomes 2 or 4 spaces.

Before

stages: [test] unit: stage: test script: [npm test]

After

stages: - test unit: stage: test script: - npm test
Paste, upload, or sample: auto-format; after a manual edit, use the Format button (⌘↵)
2- or 4-space indent (default 2) — js-yaml dump, not Prettier
Comments are not preserved — copy them first if the file depends on them
Chain format → validate for a readable review and a sound structure
YAML outside GitLab CI: beautify-yaml
include, rules, and the 512 KiB cap

A top-level include is enough to avoid the “no jobs” warning. FastMinify does not download included files. Past 512 KiB UTF-8, input is rejected — same as other DevOps tools on the site.

include counter: a scalar counts as 1; an array counts its length
Remote includes stay opaque — GitLab CI Lint merges them, the browser does not
rules: in the pasted YAML are not interpreted
Empty input stays idle, not “invalid”
A file that is too large: use glab ci lint locally

Validate, then format: a concrete workflow

Using the GitLab CI validator

Open the GitLab CI validator, paste a single .gitlab-ci.yml (max 512 KiB) and wait for debounce (~300 ms). The panel shows a verdict, issues (parse errors with a line hint), and job / stage / include counts.

Checks: root mapping, stages array, jobs with script or trigger, include, hidden jobs .
No runner, no official CI Lint schema, no remote include/rules expansion
extends-only: warning; job without script or trigger: error
Everything stays local — no account, no upload to a FastMinify server
Format YAML before the merge request

The GitLab CI formatter pretty-prints via js-yaml. Paste, upload, or sample: auto-format; after a manual edit, use the Format button. This step does not prove the structure is complete.

Clean up a job template or a poorly indented docs snippet
# comments disappear on the round-trip — copy them first if you need them
Chain format → validate for a readable review and a sound structure
For YAML outside GitLab CI, use beautify-yaml instead of this tool
Scenario — a job with neither script nor trigger

A teammate added compile: with only stage: build. GitLab refuses the pipeline, often with a message that is hard to read in the MR overlay.

1

Step 1: paste the file into validate-gitlab-ci

Open validate-gitlab-ci. An invalid verdict such as “Job … needs script or trigger” points at the empty job.

2

Step 2: add script or trigger

Add script: [npm run build] or a real trigger:. Re-paste until the panel shows a valid structure (extends warnings may remain).

3

Step 3: format and open the MR

Send the fixed YAML through format-gitlab-ci, copy, commit. Reviewers read indented jobs, not a blob.

Scenario — YAML pasted from a ticket, broken indent

An example copied from GitLab docs or a snippet arrives with mixed spaces. You can no longer see where a job ends.

1

Step 1: format first if you cannot even read the file

Paste into format-gitlab-ci. This step does not prove the config is structurally complete.

2

Step 2: validate the indented document

Copy the output into the validator. Check stages, every job, and that . templates are not hiding a broken visible job.

3

Step 3: keep CI Lint for includes and rules

Once the shape is OK, schema / remote includes / rules: stay in GitLab — FastMinify does not replace that gate.

Keep GitLab CI Lint (and minify) in the chain

glab ci lint — the schema net before you push

The browser is the fast loop: YAML plus structure. GitLab already refuses to create a pipeline if the official schema is invalid — that is why teams rarely embed a “lint CI” job in the same file. Locally, glab ci lint (official CLI, authenticated to the project) calls CI Lint: merged includes, optional --dry-run simulation. Pin your glab version; FastMinify does not replace that call. To minify JS/CSS in the pipeline, see the CI/CD minification guide.

Basic example

# Local, before git push — official GitLab CI Lint (not FastMinify). # Requires glab authenticated to the project. glab ci lint .gitlab-ci.yml # Optional: simulate pipeline creation (expands includes, uses --ref). # glab ci lint --dry-run --ref main
GitHub Actions, DevOps, unminify

If the repo is on GitHub, the counterparts are format-github-actions and validate-github-actions — same idea (YAML plus structure, not actionlint). Dockerfile and Compose stay on the DevOps hub. A minified JS bundle produced by a job is read with unminify-js or the unminify guide — that is not CI YAML.

Conclusion

Paste the .gitlab-ci.yml, fix the structure (mapping, stages, jobs with script/trigger), reindent, then push. FastMinify runs that loop locally and never uploads the YAML. It is not GitLab CI Lint: remote includes, rules:, and the official schema stay in the UI, the API, or glab ci lint. For generic YAML, stay on beautify-yaml; for GitHub Actions, on the same CI/CD hub.

Validate structure before every MR that touches .gitlab-ci.yml
Format for review, but expect YAML comments to disappear
Do not treat a FastMinify verdict as a CI Lint green light
Keep remote includes and rules: in glab / Pipeline Editor
Chain validate → format, then glab ci lint before you push
Share this article
Share this article: