Alloy syntax
You learned about components, expressions, and basic configuration elements in the previous sections. Now you’ll learn the detailed syntax rules that govern how to write Alloy configuration files.
Understanding the syntax ensures your configurations are:
- Correctly parsed: Follow rules that the Alloy parser expects.
- Maintainable: Use consistent patterns and formatting.
- Readable: Structure code clearly for yourself and your team.
- Error-free: Avoid common syntax mistakes that prevent successful evaluation.
The Alloy configuration syntax is declarative, meaning you describe what you want rather than how to achieve it. The parser evaluates all dependencies between elements automatically, so the order of blocks and attributes doesn’t matter.
Comments
Alloy configuration files support single-line // comments and block /* */ comments.
// This is a single-line comment
/*
This is a block comment
that can span multiple lines
*/Identifiers
An identifier in Alloy syntax is valid if it contains one or more UTF-8 letters, digits, or underscores. Identifiers can’t start with a digit.
The parser uses identifiers for:
- Attribute names: Keys in key-value pairs.
- Component names: Parts of block names like
prometheusinprometheus.scrape. - Component labels: User-defined names to distinguish component instances.
- Variable references: Names used in expressions and function calls.
Valid identifiers:
my_componentComponent123_privatemétrica_café(Unicode letters are supported)
Invalid identifiers:
123component(starts with digit)my-component(contains hyphen)my component(contains space)my.component(contains dot, which has special meaning)
Attributes and Blocks
Alloy configurations are built using two main syntax elements: attributes and blocks. Attributes set individual values, while blocks group related configuration and create component instances.
Attributes
Attributes configure individual settings using the format ATTRIBUTE_NAME = ATTRIBUTE_VALUE.
The parser evaluates attributes during component configuration to determine runtime behavior.
You can place attributes as:
- Top-level settings: Global Alloy configuration.
- Component arguments: Settings within component blocks.
- Nested block settings: Configuration within nested blocks.
Examples of different attribute contexts:
// Top-level attribute
log_level = "debug"
// Component block with attributes
prometheus.scrape "app" {
targets = [{ "__address__" = "localhost:9090" }]
scrape_interval = "15s"
// Nested block with attributes
basic_auth {
username = "admin"
password = sys.env("ADMIN_PASSWORD")
}
}The ATTRIBUTE_NAME must be a valid Alloy identifier.
The ATTRIBUTE_VALUE can be:
- Constant values: Strings, numbers, booleans, arrays, or objects.
- Expressions: Function calls, component references, or calculations that compute dynamic values.
Blocks
Blocks configure Alloy components and organize related settings using curly braces. The parser processes blocks to create and configure component instances.
Block structure:
- Block name: Identifies the component type (required).
- Block label: User-defined identifier to distinguish multiple instances (optional).
- Block body: Contains attributes and nested blocks (required).
Some blocks can appear multiple times in your configuration when they have different labels:
// Multiple instances of the same component type
prometheus.scrape "frontend" {
targets = [{ "__address__" = "frontend:8080" }]
}
prometheus.scrape "backend" {
targets = [{ "__address__" = "backend:9090" }]
}Nested blocks provide structured configuration:
prometheus.remote_write "production" {
endpoint {
url = "https://prometheus.example.com/api/v1/write"
basic_auth {
username = "metrics"
password = local.file.credentials.content
}
}
queue_config {
capacity = 10000
max_samples_per_send = 2000
}
}Examples
Use the following pattern to create an unlabeled block.
BLOCK_NAME {
// Block body can contain attributes and nested unlabeled blocks
IDENTIFIER = EXPRESSION // Attribute
NESTED_BLOCK_NAME {
// Nested block body
}
}Use the following pattern to create a labeled block.
// Pattern for creating a labeled block:
BLOCK_NAME "BLOCK_LABEL" {
// Block body can contain attributes and nested unlabeled blocks
IDENTIFIER = EXPRESSION // Attribute
NESTED_BLOCK_NAME {
// Nested block body
}
}Block naming rules
The parser enforces specific rules for block names and labels:
Block names must be either:
- Valid component names: Dot-separated identifiers like
prometheus.scrapeorlocal.file. - Special configuration blocks: Built-in blocks for global settings like
loggingortracing.
Block labels (when required) must be:
- Valid identifiers: Follow the same rules as attribute names.
- Double-quoted strings: Wrapped in double quotes, not single quotes.
- Unique within scope: No two blocks of the same type can have identical labels.
Example showing proper block naming:
// Component name: "local.file", Label: "api_key"
local.file "api_key" {
filename = sys.env("API_KEY_PATH")
is_secret = true
}
// Component name: "prometheus.scrape", Label: "web_servers"
prometheus.scrape "web_servers" {
targets = discovery.kubernetes.services.targets
metrics_path = "/metrics"
}The parser validates that:
- Component names exist: The component type must be available in Alloy.
- Labels are unique: Within the same component type.
- Syntax is correct: Follows the identifier and quoting rules.
Terminators
The parser requires terminators to separate statements and determine where expressions end. All block and attribute definitions must end with a newline, which Alloy calls a terminator.
A newline acts as a terminator when it follows:
- Complete expressions: After any value, calculation, or function call.
- Closing delimiters: After
],), or}. - Statement endings: After attribute assignments or block definitions.
The parser ignores newlines in other contexts, allowing flexible formatting:
// This formatting is valid - extra newlines are ignored
local.file "example" {
filename = "/path/to/file"
is_secret = true
// Comments can also have extra spacing
}
// Expressions can span multiple lines
targets = [
{ "__address__" = "server1:9090" },
{ "__address__" = "server2:9090" },
{ "__address__" = "server3:9090" }
]Formatting
Alloy provides a built-in formatter to ensure consistent code style.
Use the alloy fmt command to format your configuration files.
The formatter:
- Standardizes indentation
- Removes unnecessary whitespace
- Ensures consistent line endings
- Validates syntax
Next steps
Now that you understand the syntax fundamentals, learn how to use these elements to build working configurations:
- Components - Learn about the building blocks that collect, transform, and send data
- Expressions - Create dynamic configurations using functions and component references
For advanced configuration techniques:
- Formatting - Use
alloy fmtto automatically format your configuration files



