Cel Programs
Use for all CEL and mito work on integrations that collect from APIs — writing CEL programs, cel.yml.hbs templates, manifest configuration, mock-first development with the mito CLI, system test mock setup, and answering CEL/mito questions. Load this skill whenever any data stream uses the cel input type.
- Skill ID
- elastic/integration-skills/cel-programs
- Publisher
- elastic
- Repository
- integration-skills
- Installs
- 0
- Files
- 21
- License
- Apache-2.0
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
elastic/integration-skills/cel-programsInstalls these files- SKILL.md
- references/builder-subagent-guidance.md
- references/cel-auth-patterns.md
- references/cel-code-style.md
- references/cel-complexity-baselines.md
- references/cel-expression.md
- references/cel-function-reference.md
- references/cel-idioms.md
- references/cel-incremental-build.md
- references/cel-pagination-patterns.md
- references/cel-polymorphic-patterns.md
- references/cel-rate-limiting.md
- references/cel-system-tests.md
- references/cel-taxonomy.md
- references/cel-template-examples.md
- references/expression-builder-subagent-guidance.md
- references/mito-reference.md
- references/reviewer-subagent-guidance.md
- references/tests/map_drop.txt
- references/tests/map_merge.txt
- references/tests/map_shallow_merge.txt
What this skill tells the agent
cel-programs
When to use
Use this skill when tasks include:
- creating or editing
cel.yml.hbsagent stream templates - configuring data stream manifests for the
celinput type - writing CEL programs with pagination, cursor management, or authentication
- testing or debugging a CEL program locally with mito
- setting up system tests with mock APIs for CEL-based data streams
- prototyping a new CEL-based data stream's collection logic
- any CEL or mito question, regardless of context
When not to use
Do not use this skill as the primary guide for:
- ingest pipeline processor design (
ingest-pipelines) - ECS field mapping (
ecs-field-mappings) - package scaffolding (
create-integration) - system test execution with the Elastic stack (
integration-testing→references/system-testing.md)
Mandatory workflow — mock → mito → template
This is not a suggestion. Every CEL program MUST be developed in this order. The subagent must not write cel.yml.hbs until the CEL program has been validated with mito against a running mock. Skipping steps or reordering causes failures that are hard to debug.
Do NOT write more than ~10–15 new lines of CEL before running mito. Build the program incrementally in phases (skeleton → error handling → event mapping → pagination → cursor guard), validating with mito after each phase. Writing a large program in one shot leads to cascading compilation errors that are extremely hard to debug. Follow the phased approach in references/cel-incremental-build.md.
| Step | Action | Output | |
|---|---|---|---|
| 1. Create the system test mock | Write the elastic/stream config at _dev/deploy/docker/files/config-<stream>.yml with rules matching all API endpoints. Write test-default-config.yml. | Mock config file, docker-compose service, test config | |
| 2. Start the mock locally | stream http-server --addr=:8090 --config=... | Running mock at http://localhost:8090 | |
| 3. Create a plain `.cel` file and `state.json` | Write the CEL program as a standalone .cel file. Create state.json with the same keys the future state: block will contain, but with literal test values instead of Handlebars. Point url at the local mock. | program.cel, state.json in /tmp or working dir | |
| 4. Run mito and iterate | Build incrementally per references/cel-incremental-build.md: Phase 0 skeleton → Phase 1 error handling → Phase 2 events → Phase 3 pagination → Phase 4 cursor. Run mito -data state.json -log_requests program.cel after each phase. Do not proceed until mito output is correct. | Validated CEL program | |
| 5. ONLY THEN write `cel.yml.hbs` | Copy the working CEL expression into `program: \ | in the Handlebars template. Replace literal test values with {{var}}` references. Configure manifests. | Final integration template |
Step 3 detail — translating template vars to mito state: When the future cel.yml.hbs will have a state: block like api_key: {{api_key}} and batch_size: {{batch_size}}, the state.json for mito testing uses the same key names with literal test values:
{
"url": "http://localhost:8090",
"api_key": "test-key",
"batch_size": 50,
"initial_interval": "24h"
}This mirrors the runtime state the CEL input would provide. Add cursor to test subsequent-run behavior.
For the full mock-first workflow details, CLI flags, execution model, and quality standards: load references/mito-reference.md.
cel.yml.hbs template anatomy
The cel.yml.hbs file at data_stream/<stream>/agent/stream/cel.yml.hbs is a Handlebars template that renders the final CEL input configuration. It has these sections in order:
interval: {{interval}}
resource.tracer:
enabled: {{enable_request_tracer}}
filename: "../../logs/cel/http-request-trace-*.ndjson"
maxbackups: 5
{{#if proxy_url}}
resource.proxy_url: {{proxy_url}}
{{/if}}
{{#if ssl}}
resource.ssl: {{ssl}}
{{/if}}
{{#if http_client_timeout}}
resource.timeout: {{http_client_timeout}}
{{/if}}
resource.url: <constructed from vars>
state:
<credentials and pagination config from vars>
redact:
fields:
- <sensitive state keys>
max_executions: <number, for heavy pagination>
program: |
<CEL expression>
tags:
{{#if preserve_original_event}}
- preserve_original_event
{{/if}}
{{#each tags as |tag|}}
- {{tag}}
{{/each}}
{{#contains "forwarded" tags}}
publisher_pipeline.disable_host: true
{{/contains}}
{{#if processors}}
processors:
{{processors}}
{{/if}}Handlebars patterns
| Pattern | Purpose | ||
|---|---|---|---|
{{var_name}} | Direct variable substitution | ||
{{#if var_name}}...{{/if}} | Conditional block for optional config | ||
| `{{#each tags as \ | tag\ | }}` | Iteration over list vars |
{{#contains "forwarded" tags}} | Check if list contains value |
Key template fields
resource.url— base URL, often constructed from multiple vars (e.g.,{{url}}/api/v1/endpoint)resource.headers(ga 8.18.1) — static headers the same for every request (Content-Type,Accept, API version headers). Set here rather than in-program when headers never vary. Applied before auth headers.state:— block where manifest vars are injected as CEL state; credentials and pagination settings go hereredact.fields— list state keys containing secrets to redact from debug logsmax_executions— override default 1000 for integrations with heavy pagination (e.g., 5000)program: |— the CEL expression; must be a YAML literal block scalar
Do NOT set data_stream.dataset in integration packages
Integration packages (type: integration) must never include data_stream.dataset in cel.yml.hbs or define a data_stream.dataset manifest var. The framework automatically routes documents to the correct data stream. Setting data_stream.dataset overrides this routing and causes documents to land in the wrong index — typically resulting in "0 hits" during system tests.
Only input-type packages (type: input) use data_stream.dataset because they have no predefined data streams.
Data stream manifest configuration
The data stream manifest.yml defines the CEL input stream and its variables.
Standard vars every CEL stream should include
| Var | Type | Purpose |
|---|---|---|
url | text | API base URL |
interval | text | Polling interval (e.g., 5m) |
initial_interval | text | Lookback window on first run (e.g., 24h) |
enable_request_tracer | bool | Enable HTTP request tracing |
http_client_timeout | text | Request timeout (e.g., 30s) |
proxy_url | text | HTTP proxy URL |
ssl | yaml | TLS configuration |
tags | text (multi) | Event tags |
preserve_original_event | bool | Keep original event |
processors | yaml | Beat processors |
Auth-specific vars depend on the API (API key, OAuth client_id/secret/token_url, bearer token, etc.).
Declare enable_request_tracer in the data stream manifest, not at the input level. Input-level tracing enables logging for all data streams in the policy.
Package-level vs data-stream-level vars
- Package-level vars in the root
manifest.ymlunderpolicy_templates[].inputs[].vars: shared across streams (e.g.,url, auth credentials) - Data-stream-level vars in
data_stream/<stream>/manifest.ymlunderstreams[].vars: stream-specific (e.g.,interval,batch_size,initial_interval)
Scope of the CEL program
The CEL program's responsibility is data collection only:
- Fetch data from the API endpoint(s)
- Handle pagination — walk through all pages within a single polling cycle
- Manage cursor state — store timestamps or page tokens in
cursorso the next polling interval resumes where the last one left off, avoiding re-collection of already-fetched events - Emit raw events — output
{"message": e.encode_json()}for each record
The CEL program does not handle:
More from elastic
Use this skill in a project
Open any RiverX project, search the Skills panel for elastic/integration-skills/cel-programs, and install it in one click.
