Execution environment for CEL expressions. Holds variable bindings, custom function definitions, and an optional type adapter.
Building an environment
env = Celixir.Environment.new(%{x: 10, name: "alice"})Registering custom functions
Use put_function/3 to register Elixir functions callable from CEL.
Functions receive plain Elixir values (unwrapped from CEL internal types)
and should return plain Elixir values.
env = Celixir.Environment.new()
|> Celixir.Environment.put_function("double", fn x -> x * 2 end)
|> Celixir.Environment.put_function("math.clamp", fn v, lo, hi ->
v |> max(lo) |> min(hi)
end)You can also pass module function captures:
env = Celixir.Environment.new()
|> Celixir.Environment.put_function("slugify", &MyApp.Helpers.slugify/1)Building reusable libraries
Group related functions into a module that configures an environment:
defmodule MyApp.CelLibrary do
alias Celixir.Environment
def register(env \\ Environment.new()) do
env
|> Environment.put_function("format.currency", &format_currency/2)
|> Environment.put_function("format.percent", &format_percent/1)
end
defp format_currency(amount, cur), do: "#{cur} #{amount}"
defp format_percent(ratio), do: "#{round(ratio * 100)}%"
end
env = MyApp.CelLibrary.register()
|> Celixir.Environment.put_variable("price", 29.9)
Celixir.eval!("format.currency(price, 'USD')", env)
Summary
Functions
Deletes a private value from the environment.
Looks up a function.
Retrieves a private value from the environment.
Retrieves a private value, raising if the key doesn't exist.
Looks up a variable with proper resolution order
Checks if a variable name is locally bound (e.g., comprehension iter var).
Creates a new empty environment.
Creates an environment with the given variable bindings.
Registers a custom function.
Adds a local variable binding that shadows container-resolved and outer names.
Stores a private value in the environment.
Adds a variable binding.
Sets the container (namespace) for identifier resolution.
Sets a custom type adapter module.
Types
@type t() :: %Celixir.Environment{ container: String.t() | nil, container_prefixes: [String.t()], functions: %{required(String.t()) => function()}, locals: %{required(String.t()) => any()}, private: %{required(any()) => any()}, type_adapter: module() | nil, variables: %{required(String.t()) => any()} }
Functions
Deletes a private value from the environment.
Looks up a function.
Retrieves a private value from the environment.
Returns {:ok, value} or :error.
Retrieves a private value, raising if the key doesn't exist.
Looks up a variable with proper resolution order:
- Absolute names (
.y) bypass locals and container, look up in outer variables only - Local names check locals first (comprehension iter vars, cel.bind), then container-resolved outer vars
Checks if a variable name is locally bound (e.g., comprehension iter var).
Creates a new empty environment.
Creates an environment with the given variable bindings.
Registers a custom function.
Adds a local variable binding that shadows container-resolved and outer names.
Stores a private value in the environment.
Private values are not visible to CEL expressions — they are only accessible from custom Elixir functions that receive the environment.
Examples
env = Celixir.Environment.new()
|> Celixir.Environment.put_private(:repo, MyApp.Repo)
Adds a variable binding.
Sets the container (namespace) for identifier resolution.
Sets a custom type adapter module.