In a GitLab CI/CD component, what does a spec:inputs header give you that configuring an included template through global variables does not?
answer
- a declared contract, not a naming convention
- header document above the three dashes
- substituted before the YAML is parsed
- options and regex checked at creation time
- scoped to one include, not the pipeline
basics
~20 sspec:inputs declares typed, validated parameters with defaults and allowed values, interpolated into the template as $[[ inputs.name ]] before the configuration is parsed. Invalid or missing values fail at pipeline creation, and the values are scoped to that one include rather than leaking into every job.
solid answer
~50 sA template configured by global variables has no contract: the consumer sets `SCAN_SEVERITY` somewhere, hopes the template reads that exact name, and a typo produces a job that runs with a wrong or empty value. `spec:inputs` makes the contract explicit. The component file starts with a `spec:` header, separated from the configuration by `---`, declaring each input with a `default`, a `description`, a `type`, and optionally `options` or a `regex`. The consumer passes them under `inputs:` on the include, and GitLab interpolates `$[[ inputs.name ]]` **before** the YAML is parsed — so an input can supply a job name, a stage, or any key, not just a value. Anything missing, mistyped or outside `options` is a pipeline-creation error with a clear message. Inputs are also scoped to that include, so two includes of the same component with different inputs do not collide the way two global variables would.
code
yaml · 14 linesspec:
inputs:
stage:
default: test
description: Stage the scan job runs in
severity:
type: string
default: high
options: [low, medium, high]
---
scan:
stage: $[[ inputs.stage ]]
script:
- scanner run --fail-on $[[ inputs.severity ]]go deeper
Know that a component declares its parameters in a spec:inputs header and the consumer passes them under inputs: on the include, instead of setting global variables and hoping the names line up.
Explain interpolation versus variables: $[[ inputs.x ]] is substituted before parsing so it can set a stage or job name, while $VAR is expanded by the runner. Name the validation features — type, default, options, regex — and say that violations fail pipeline creation.
Show why this changes operations: a typo becomes a creation-time error instead of a ten-minute job failure, two includes of the same component no longer collide, and pinning @1.2.0 makes template behaviour reproducible.
Own the component contract across the organisation: versioning policy for inputs, how a required input is added without breaking consumers, and whether teams consume catalog components or a template repository pinned by tag.
## The problem with variable-configured templates The classic GitLab shared template takes its configuration from CI/CD variables: ```yaml # in the template scan: script: - scanner run --fail-on "$SCAN_SEVERITY" ``` The consumer sets `SCAN_SEVERITY` in their `variables:` block, in project settings, or not at all. Every failure mode here is silent: a typo gives an empty string; a value the scanner does not accept is discovered when the job runs, minutes later; the variable is global, so it also lands in every unrelated job's environment; and including the template twice with different settings is impossible, because there is one variable namespace. ## What `spec:inputs` changes A component or template file may begin with a **spec header**, a separate YAML document ended by `---`: ```yaml spec: inputs: stage: default: test description: Stage the scan job runs in severity: type: string default: high options: [low, medium, high] --- scan: stage: $[[ inputs.stage ]] script: - scanner run --fail-on $[[ inputs.severity ]] ``` The consumer supplies values on the include itself: ```yaml include: - component: $CI_SERVER_FQDN/platform/ci-components/[email protected] inputs: severity: medium ``` Five differences matter. **It is interpolation, not a variable.** `$[[ inputs.severity ]]` is substituted into the YAML text *before* GitLab parses the configuration. That is why an input can decide a stage name, a job name, an `image:`, or whether a whole block exists — places where a `$VARIABLE` would be meaningless because the value is needed at configuration time, not at runtime. Note the distinct syntax: `$[[ ... ]]` is interpolation, `$VAR` is a runtime variable, and they are resolved at different moments. **It is validated.** `type` (string, number, boolean, and array in newer versions), `options` for an enumeration, and `regex` for a string pattern are all checked while the pipeline is being created. A bad value produces a configuration error on the pipeline page — no jobs start — instead of a job that fails ten minutes in. **It has declared defaults and required inputs.** An input with no `default` is mandatory: omit it and pipeline creation fails naming the input. That turns "which variables does this template need?" from tribal knowledge into a machine-checked contract, and `description` makes the file self-documenting. **It is scoped to the include.** Two includes of the same component with different inputs are independent, because nothing is written into the global variable namespace. Variable-configured templates cannot do this at all. **It composes with versioning.** `include:component` addresses a component published in the project's `templates/` directory and released to the CI/CD catalog, pinned by version: `@1.2.0` for an exact release, a branch or commit for development, or `@~latest` for the newest release. A component's inputs are therefore versioned along with its behaviour, which is what makes a breaking change to a template a *semver* event rather than a surprise. ## Inputs are not exclusive to components A common misconception: `inputs:` also works with `include:local`, `include:project` and `include:remote` — any included file that declares a `spec:` header can be given `inputs:`. Components add the catalog, the versioning scheme and a discoverable published surface; the parameter mechanism itself is shared. ## Where it still bites Interpolation happens before parsing, so an input value containing YAML-significant characters can change the shape of the document — quote where it matters. Interpolation also only applies inside the file that declared the spec; you cannot reach `inputs` from an unrelated file. And because everything is resolved at pipeline creation, an input cannot depend on anything a job computes; that remains the job of `rules:` or a child pipeline. ## The interview shape Asked as "how do you make a shared template configurable without a wall of global variables?", the expected answer is: declare a spec header with typed inputs, interpolate them with `$[[ inputs.x ]]`, publish the file as a versioned component, and let consumers pass `inputs:` on the include. The follow-up is usually about migration — you can add a spec header to an existing template and keep reading the old variables for a release or two while consumers move.
- How does $[[ inputs.x ]] differ from $X inside a component file?`$[[ inputs.x ]]` is interpolated into the YAML text before GitLab parses the configuration, so it can supply a stage name, a job name or an image. `$X` is a CI/CD variable expanded by the runner when the job executes, so it can only ever appear inside a value that is used at runtime. Different mechanisms, different moments.
- What happens if a consumer omits an input that has no default?Pipeline creation fails with a configuration error naming the missing input, and no jobs run. That is the point: an input without a `default` is a required parameter, so the template's requirements are enforced by GitLab rather than documented in a README nobody reads.
- Do you have to publish a component to use typed inputs?No. Any included file — local, project or remote — can declare a `spec:` header and receive `inputs:` on the include. Publishing to the CI/CD catalog adds discovery and version pinning with `@1.2.0` or `@~latest`, which is what makes a breaking input change a semver event rather than a surprise for every consumer.
saying these in an interview costs you the question
- Thinking inputs are just CI/CD variables with nicer syntax
- Believing inputs only work with catalog components
- Expecting $[[ inputs.x ]] to resolve when the job runs
- Assuming a wrong input value fails inside the job
- Passing configuration as global variables that leak everywhere