HAProxy Language SupportSchema-driven language support for HAProxy configuration files in Visual Studio Code and compatible editors. Open any FeaturesSyntax highlightingColorization is generated from HAProxy's own keyword inventory (
Intelligent completionSuggestions follow where you are in the file:
Completion reloads immediately when you change the configured HAProxy version or edition.
Inline documentationHover any supported keyword to read summaries sourced from HAProxy's official
Log-format supportFormat strings in directives such as
Real-time diagnosticsCatch common mistakes while you type:
Diagnostics are schema-based — they help you write valid-looking config faster, but they do not replace Suppress a specific same-line diagnostic with Missing-reference warnings (on by default via Unused symbol hints (on by default via
Document formattingRun Format Document (or enable format-on-save) to normalize layout according to HAProxy's configuration file rules:
Indent style (4 spaces, 2 spaces, or tab) and blank-line behavior are configurable — see Settings below.
Outline and foldingNavigate large configs with built-in structure support:
Workspace symbol graphSplit HAProxy layouts — separate files for frontends, backends, ACLs, or shared defaults — are indexed into a workspace symbol graph (on by default via
The graph rebuilds after workspace file changes (debounced via Glob discovery vs HAProxy
|
| Workspace graph | HAProxy process | |
|---|---|---|
| File discovery | VS Code globs under workspace folders | Explicit -f paths and directories |
| Load order | All matching files indexed together | Order of -f arguments matters |
include directives |
Not followed | HAPEE / some layouts follow includes |
| Fragment files | Need section headers in each indexed .cfg |
Can be spliced after a file that opened a section |
Use haproxy.workspaceSymbols.roots to narrow discovery inside a workspace folder to known config trees (for example haproxy.d or etc/haproxy). It does not create a separate graph per subdirectory; put independent configs in separate VS Code workspace folders. Narrow include when unrelated .cfg files share names and would collide on cross-file rename.
Go to definition, find references, and rename
Jump across related config with standard editor navigation (Go to Definition, Go to References, Rename Symbol, peek view):
- Frontends / backends / listen —
use_backend,default_backend, and section headers link to the matching proxy section; Go to Definition on a section highlights the full section body, not just the header line - ACLs — definitions and uses in
if/unlessconditions within the same section (including negated forms like!is_api), chained implicit-AND references (if is_static !is_image), inline{ … }conditions, and compound&&/||expressions - Servers —
serverlines anduse-serverreferences inside a backend or listen - Defaults profiles —
defaults … from <profile>links to the named profile - Filters, cache, userlist, resolvers, peers — section and statement definitions indexed from the schema
- Environment variables —
setenv/presetenvdefinitions and references fromunsetenv/resetenv, double-quoted$VAR/${VAR}/${VAR-default}/${VAR-sub}, andenv(VAR)sample fetches. With the workspace graph active, Go to Definition, Find References, and Rename follow those names across indexed.cfgfiles - Runtime variables —
set-var/set-var-fmtdefinitions andvar(...)/%[var(...)]/unset-var(...)references (file-wide and across the workspace graph). Hover on the name peeks the definingset-varline; hover onset-var/varstill shows action and fetch docs - Rename Symbol (F2) — with the workspace graph active, rename updates matching definitions and references across indexed
.cfgfiles for backends, ACLs, defaults profiles, servers, filters, environment variables, runtime variables, and related named sections. Invalid names and same-scope collisions (including an existing name in another indexed file) are rejected
Reference resolution is schema-driven via reference patterns in the bundled language data, not hardcoded heuristics. With the workspace graph active, definitions, references, and rename can span multiple .cfg files. Narrow scope with haproxy.workspaceSymbols.include or disable workspace symbols when independent configs share names — duplicate section names across unrelated files can make cross-file rename affect more than you intend. When the graph is disabled or over configured limits, rename falls back to the current file


Getting started
- Install the extension from the Marketplace (or load a
.vsixlocally). - Open a HAProxy config (
.cfgextension is recognized automatically;#line comments, bracket matching, and auto-closing pairs are enabled). For cross-file navigation and workspace symbol diagnostics, open a workspace folder containing your.cfgfiles. - Choose your HAProxy version and edition so completion, hover, diagnostics, formatting, and highlighting match your deployment (see below).
No extra runtime is required for day-to-day editing — schemas and grammars ship with the extension. If bundled schema or language data fails to load, the extension shows a one-time error notification.
HAProxy version and edition
Pick the community or Enterprise release that matches the binaries you operate:
| Version | Community | HAPEE |
|---|---|---|
| 3.2 | Yes (default) | 3.2r1 |
| 3.4 | Yes | — (not released yet) |
| 3.0 | Yes | 3.0r1 |
| 2.8 | Yes | 2.8r1 |
| 2.6 | Yes | 2.6r1 |
HAPEE (HAProxy Enterprise) is a separate edition of the matching LTS: pick 3.2 HAPEE (or 2.6 / 2.8 / 3.0 HAPEE) in the version picker, or set haproxy.edition to hapee. That loads the haproxy-X.Yr1 schema, language data, and TextMate grammar, including core Enterprise syntax and release-gated modules (WAF, response-body injection, UDP, SAML, Captcha, Bot Management, OIDC, and RHI). Community OSS 3.4 has no HAPEE artifacts yet.
Schemas for 2.6 and 2.8 are generated from the legacy configuration.txt layout (actions listed under each ruleset in §4.2 rather than §4.3/§4.4). Completion, diagnostics, and hover reflect keywords available in that release.
Ways to change version and edition:
- Status bar — click HAProxy while a
.cfgfile is active. - Command Palette — run HAProxy: Select HAProxy Version and Edition.
- Settings — set HAProxy: Version (
haproxy.version) and HAProxy: Edition (haproxy.edition).
Completion, diagnostics, hover, and highlighting update as soon as the setting changes. Syntax highlighting switches the active TextMate grammar (haproxy-3.2 vs haproxy-3.2r1); if colors do not refresh, use Developer: Reload Window when prompted.

Settings
| Setting | Default | Description |
|---|---|---|
haproxy.version |
3.2 |
HAProxy release used for completion, diagnostics, hover, and syntax highlighting. Pair with haproxy.edition for HAPEE. |
haproxy.edition |
community |
community or hapee. HAPEE loads Enterprise schema, language, and grammar files (haproxy-X.Yr1) for 2.6–3.2. Ignored for 3.4 until HAPEE 3.4 exists. |
haproxy.diagnostics.enabled |
true |
Turn off if opening very large .cfg files feels slow |
haproxy.diagnostics.debounceMs |
500 |
Delay after edits before recomputing diagnostics (100-5000 ms) |
haproxy.diagnostics.maxLines |
4000 |
Skip diagnostics above this line count to limit memory use |
haproxy.symbols.maxLines |
4000 |
Skip per-document symbol indexing above this line count; navigation, rename, symbol completion, and semantic highlighting are disabled for larger files. |
haproxy.diagnostics.deprecatedWarnings |
true |
Warn on directives and rule actions marked (deprecated) in the official docs. Warnings are suppressed when global contains expose-deprecated-directives. |
haproxy.diagnostics.unusedSymbols |
true |
Hint and fade unused ACL lines, unused set-var / set-var-fmt lines, and unreferenced section blocks in the current file (Ty-style unnecessary-code styling). Turn off if you prefer a cleaner Problems panel. |
haproxy.diagnostics.missingReferences |
true |
Warn when a named reference (ACL, backend, cache, userlist, resolvers, peers, defaults profile, runtime variable) has no definition in the current file or workspace graph. |
haproxy.workspaceSymbols.enabled |
true |
Build a workspace-level symbol graph for cross-file navigation and symbol diagnostics across split .cfg layouts. |
haproxy.workspaceSymbols.include |
["**/*.cfg"] |
Glob patterns for HAProxy files included in the workspace symbol graph. |
haproxy.workspaceSymbols.exclude |
see description | Glob patterns excluded from workspace indexing (default: .git, node_modules, dist, out, vendor). |
haproxy.workspaceSymbols.roots |
[] |
Optional subdirectory prefixes inside each VS Code workspace folder (for example haproxy.d). Empty means the whole folder. Multi-root folders already get separate graphs; this does not follow HAProxy -f. |
haproxy.workspaceSymbols.maxFiles |
500 |
Optional maximum indexed files per VS Code workspace folder; 0 means unlimited. |
haproxy.workspaceSymbols.maxTotalLines |
200000 |
Optional maximum total indexed lines per VS Code workspace folder; 0 means unlimited. |
haproxy.workspaceSymbols.maxFileBytes |
2097152 |
Optional maximum bytes per indexed HAProxy file (2 MiB); 0 means unlimited. |
haproxy.workspaceSymbols.maxTotalBytes |
52428800 |
Optional maximum total indexed bytes per workspace folder (50 MiB); 0 means unlimited. |
haproxy.workspaceSymbols.maxLineBytes |
65536 |
Optional maximum encoded bytes per line in an indexed file; 0 means unlimited. |
haproxy.workspaceSymbols.debounceMs |
750 |
Delay after workspace file changes before rebuilding the symbol graph (100-10000 ms). |
haproxy.format.enabled |
true |
Enable Format Document for HAProxy configs |
haproxy.format.indent |
spaces-4 |
Indentation inside sections: spaces-4, spaces-2, or tab |
haproxy.format.insertBlankLineBetweenSections |
true |
Insert a blank line before each new section header when formatting |
The extension also raises editor.maxTokenizationLineLength for HAProxy files so long server / bind lines tokenize correctly.
Pre-0.12 settings haproxy.format.indentStyle and haproxy.format.indentSize are still honored as a fallback when haproxy.format.indent is unset.

Commands
| Command | Description |
|---|---|
| HAProxy: Select HAProxy Version and Edition | Quick-pick between community 2.6–3.4 and HAPEE 2.6r1–3.2r1 |
| HAProxy: Open Workspace Symbol Settings | Opens workspace symbol graph settings (include, roots, caps, debounce) |
How it works
Language data is built offline from upstream sources:
configuration.txt— descriptions and documentation structure per community HAProxy release.haproxy -dKall— the complete keyword list emitted by the matching OSS binary.- HAPEE configuration manuals — Enterprise HTML for 2.6r1–3.2r1, merged on top of the OSS base.
Those inputs are merged into JSON schemas, completion/hover payloads, and TextMate grammars (see the companion haproxy-schema repository). The VS Code extension loads the bundled artifacts for the version and edition you select — no Python or local HAProxy install needed to use the extension.
Performance
The extension is built for interactive editing. The table below shows median timings from our automated micro-benchmarks (npm run bench) on Node.js 24 - they exercise the same TypeScript code paths as the extension host, using bundled schemas and synthetic .cfg fixtures.
| Operation | Small config (~18 lines) | Medium config (~100 lines) | Stress config (24,000 lines) |
|---|---|---|---|
Startup - load schema + language data (first .cfg open) |
- | - | ~19 ms |
| Syntax highlighting - full grammar tokenize[^1] | ~7 ms | ~11 ms | ~1.8-2.2 s |
| Diagnostics - one full pass[^2] | ~0.1 ms | ~0.8 ms | ~210-360 ms |
| Diagnostics - incremental edit revalidation[^2] | ~0.02 ms | ~0.08 ms | ~20 ms |
| Diagnostics + unused-symbol hints | - | - | ~210-360 ms |
| Diagnostics + unused-symbol hints - incremental edit[^2] | - | - | ~25 ms |
| Format document | <0.01 ms | ~0.08 ms | ~30 ms |
| Completion at cursor | <0.01 ms | - | ~19 ms |
| Hover | <0.1 ms | - | <0.1 ms |
| Go to definition / references | <0.01 ms | - | ~1 ms |
What this means in practice
- Everyday configs (hundreds to a few thousand lines) stay responsive: diagnostics, completion, and hover are sub-millisecond to low tens of milliseconds per operation.
- Incremental diagnostics are now the fast path during editing. On the 24k-line stress fixtures, a single-line edit revalidates in about 20 ms without unused-symbol hints and 25 ms with them enabled. The same edits take about 236 ms and 361 ms respectively when forced through a full recompute baseline.
- Diagnostics still dominate full-pass cost on very large files - the main reason
haproxy.diagnostics.maxLinesdefaults to 4000 and very large files skip validation unless you raise that limit. CI guards the incremental stress-edit path at p99.5 under 45 ms without unused-symbol hints and 40 ms with them (robust thresholds from 21 CI runs), while full-pass stress benchmarks remain guarded separately. - Highlighting scales with file size; the editor tokenizes incrementally, so the stress numbers above are a full-file worst case, not what you pay on every keystroke. Grammars are line-isolated (no
begin/endregion may carry state past end-of-line), so tokenization cost reflects correct per-line highlighting even when earlier lines contain deliberate syntax errors. - Stress fixtures:
large-valid.cfg(mostly valid) tokenizes at ~2.2 s median;large-mixed.cfg(valid baseline plus injected invalid lines every ~5 blocks) at ~1.8 s median. The p99.5 tokenization thresholds are 2.7 s and 2.2 s respectively. - Startup pays a one-time ~19 ms JSON parse when the extension first loads language data for your selected HAProxy version and edition; the p99.5 threshold is 30 ms.
CI runs these benchmarks on every push (npm run bench:ci) and tracks regressions against test/bench/thresholds.json. To reproduce locally:
npm run bench
[^1]: Measured with vscode-textmate against the shipped grammar - a proxy for editor highlighting cost.
[^2]: After haproxy.diagnostics.debounceMs (default 500 ms) following each edit in the real editor.
Report issues
Found a false positive, missing completion, or wrong hover text? Open an issue on GitHub.
Required information — issues without these details are hard to reproduce and may be closed:
- Offending config — paste the exact line(s) or a minimal snippet that triggers the problem (redact secrets; keep structure intact).
- Error or unexpected behavior — copy the full diagnostic message from the Problems panel, or describe what you expected vs. what happened (e.g. no squiggle, wrong completion list).
Helpful context (include when relevant):
- HAProxy: Version (
haproxy.version) and HAProxy: Edition (haproxy.edition) — e.g.3.2community or3.2HAPEE - Extension version and editor (VS Code version)
- Whether
haproxy -caccepts or rejects the same config on your binary
Contributing
The extension repo is self-contained for CI: unit and integration tests use bundled schemas under schemas/ and config snippets under test/fixtures/. No sibling checkout is required to run npm test or npm run test:coverage.
Schema generation and upstream config corpus validation live in the companion haproxy-schema repository. Optional monorepo checkouts are only for regeneration and extended local validation:
parent/
haproxy-vscode/ # this extension (CI runs here)
haproxy-schema/ # schema & grammar generator (python -m haproxy_schema)
haproxy_git/ # optional: upstream HAProxy trees for regeneration & test:upstream
haproxy-2.6/
haproxy-2.8/
haproxy-3.0/
haproxy-3.2/
haproxy-3.4/
Extension
From haproxy-vscode/:
npm install
npm run compile
compile only builds TypeScript. HAProxy version-specific schema/language data is loaded at extension startup from haproxy.version (default 3.2) and haproxy.edition (default community), and grammar switching is handled by the extension when the version or edition changes.
Use Run HAProxy Extension in the Run and Debug view after compiling.
Lint and format (enforced in CI):
npm run lint
npm run format:check
npm run format # auto-fix formatting
npm test
Runs Vitest unit tests and VS Code Extension Development Host integration tests. Tests load bundled schemas and fixtures from test/fixtures/ (including curated upstream snippets in test/fixtures/golden/). For coverage only:
npm run test:coverage
For extended local validation (grammar check, full upstream scans, haproxy -c comparison) when sibling repos are present:
npm run test:all
Optional upstream-only scripts (require sibling haproxy_git/):
npm run test:upstream
npm run compare:haproxy:matrix
npm run compare:haproxy:docker:matrix
compare:haproxy:matrix runs haproxy -c parity checks for all supported versions (2.6, 2.8, 3.0, 3.2, 3.4) against matching upstream tests/conf directories.
compare:haproxy:docker:matrix uses Docker images haproxy:<version>-trixie as ground truth and checks both tests/conf/*.cfg and examples/*.cfg for each version.
For a single version, call the script directly:
node scripts/compare-haproxy-c.mjs --version 3.2
To run schema pytest plus extension tests from a monorepo layout:
.\haproxy-schema\scripts\test-all.ps1
Regenerating schemas
Set PYTHONPATH to the haproxy-schema repo root, then from haproxy-vscode/:
$env:PYTHONPATH = (Resolve-Path "..\haproxy-schema").Path
npm run generate:schema
npm run compile
generate:schema regenerates every supported community version (2.6, 2.8, 3.0, 3.2, 3.4), then the HAPEE schema, language, and grammar files (haproxy-X.Yr1.*). Community grammars stay in haproxy-X.Y.tmLanguage.json; HAPEE grammars are written to haproxy-X.Yr1.tmLanguage.json. You can regenerate one community version with npm run generate:schema:<version> or all HAPEE artifacts with npm run generate:schema:hapee. HAPEE source downloads are checksum-pinned; an upstream manual change stops generation until it is reviewed and deliberately pinned. To refresh keyword dumps (requires a DEBUG build of the matching HAProxy binary in haproxy_git/):
npm run generate:dkall:2.6
npm run generate:dkall:2.8
npm run generate:dkall:3.2
See haproxy-schema README for dkall generation, binary installation, pytest, and upstream golden-config validation.
Packaging
npm run package
Produces a .vsix via @vscode/vsce (vscode:prepublish compiles TypeScript automatically).
License
MIT. See NOTICE for third-party and data-source attributions.
Bundled files under schemas/ and syntaxes/ are generated from HAProxy configuration.txt and haproxy -dKall output via the companion haproxy-schema project (Apache-2.0). Community documentation excerpts in hover and completion payloads are derived from HAProxy's official configuration reference (GPL-2.0-or-later). HAPEE artifacts additionally use the Enterprise configuration manuals published at haproxy.com. Keyword-line parsing in haproxy-schema is aligned with haproxy-dconv (Apache-2.0).













