Trigger and scope
Trigger on: XVCL, .xvcl files, VCL transpiler, VCL metaprogramming, #const/#for/#def/#inline in VCL context, writing a VCL script, writing VCL and running it locally, or any Fastly VCL writing task.
Do NOT trigger for: debugging existing .vcl files without XVCL, Fastly API/CLI ops, Fastly Compute, or Terraform — even if they mention VCL.
If the user explicitly requests plain VCL, keep .vcl files and do not introduce XVCL.
Follow explicitly requested tools and test modes instead of the default examples.
Writing VCL with XVCL
XVCL is a VCL transpiler that adds metaprogramming to Fastly VCL. Write .xvcl files, compile to .vcl, then test with Falco or deploy to Fastly. All XVCL constructs are resolved at compile time — zero runtime overhead.
Quick Start
Compilation requires uvx, provided by uv; the local checks use Falco.
Unless the user explicitly requests another test mode, compile and run falco simulate for local execution.
Linting alone does not execute the VCL.
Minimal Working Example
Backend naming: Fastly VCL requires backends to use F_ prefixed names (e.g., F_origin, F_api). Never use backend default — falco will reject it. Always set req.backend explicitly in vcl_recv.
XVCL Directives Summary
Read xvcl-directives.md [blocked] for complete syntax and examples of every directive.
Constants — #const
Constants are compile-time only — they do NOT become VCL variables. Always use {{NAME}} to emit their value. A bare constant name in VCL (e.g., error 200 GREETING;) passes through as a literal string, producing invalid VCL. Use error 200 "{{GREETING}}"; instead.
Use in templates: "{{TTL}}", {{ORIGIN}}, backend F_{{name}} { ... }
Template Expressions — {{ }}
Built-in functions: range(), len(), str(), int(), hex(), format(), enumerate(), min(), max(), abs()
For Loops — #for / #endfor
Tables with Loops
Use #for loops to populate VCL table declarations for O(1) lookups, instead of generating inline if-chains.
Prefer populating VCL table declarations with #for loops over generating inline if-chains. Tables give O(1) hash lookups and are the idiomatic Fastly pattern for any data-driven routing, redirects, or configuration.
Conditionals — #if / #elif / #else / #endif
Supports: boolean constants, comparisons (==, !=, <, >), operators (and, or, not).
Variable Shorthand — #let
Functions — #def / #enddef
Functions compile to VCL subroutines with parameters passed via req.http.X-Func-* headers.
Inline Macros — #inline / #endinline
Includes — #include
Include-once semantics. Circular includes are detected and reported.
Compilation
Common Mistakes
- Bare constant names in VCL:
error 200 GREETING;passes through as a literal string. Useerror 200 "{{GREETING}}";with template syntax. - Generating if-chains instead of tables: When you have data-driven routing or redirects, always populate a VCL
tablewith#for— not an inline if-chain. If-chains are O(n); tables are O(1). - Forgetting
#FASTLYmacros: Every VCL subroutine (vcl_recv,vcl_fetch,vcl_deliver,vcl_error,vcl_hit,vcl_miss,vcl_pass) needs#FASTLY recv(or the appropriate name) at the top. - Using
backend default: Fastly VCL requiresF_prefixed backend names. Usebackend F_origin { ... }andset req.backend = F_origin;.
VCL Gotchas
VCL runtime pitfalls that are easy to get wrong:
- No modulo operator: VCL has no
%operator. For traffic splitting, usesubstr()on a hash digest:if (substr(digest.hash_sha256(client.ip), 0, 1) ~ "^[0-7]$")gives ~50%. Or userandomint(0, 99) < 50. - Vary MUST be set in
vcl_fetch: The Vary header controls the cache key. Setting it only invcl_deliveris too late — the object is already cached without Vary dimensions. Always append Vary invcl_fetch(and optionally mirror invcl_deliverfor client-visible headers). Never overwrite existing Vary: check and append. req.url.pathis read-only in falco tests: In test subroutines, useset req.url = "/path"instead ofset req.url.path = "/path". The.pathproperty is computed fromreq.urland cannot be set directly.req.requestis deprecated: Usereq.methodinstead. Falco accepts both butreq.methodis the modern form.- Cookie parsing: Use
subfield(req.http.Cookie, "name", ";")instead of regex. Regex likeCookie ~ "name=(\w+)"false-matches cookies with similar prefixes (e.g.,name_v2=X).
References
For VCL basics (request lifecycle, return actions, variable types), see the VCL syntax and subroutines references below.
Read the relevant reference file completely before implementing specific features.


