ecs-field-mappings
When to use
Use this skill when tasks include:
- adding or modifying files under
data_stream/<stream>/fields/
- populating with ECS field references
- selecting , , , and values
- choosing field and mapping properties (, , , and related options)
- checking whether a field already exists in ECS before adding custom fields
- troubleshooting mapping validation/build failures from , , or pipeline test schema checks
ECS dependency configuration
Every package needs
at the package root. This file pins the ECS schema version used for field resolution.
yaml
dependencies:
ecs:
reference: "git@v9.3.0"
This file is
required whenever the package has any field file. The scaffold does not generate it — create it manually. If it is missing or uses an outdated version, tests report ECS fields as undefined (e.g.,
field "destination.ip" is undefined
).
Field files and roles
A data stream's
directory contains a small set of YAML files with distinct responsibilities:
Fixed routing constants and
. All six fields are ECS fields, so each entry uses
. Override
and
where the data stream needs a
with a fixed value — the description is inherited from ECS automatically.
yaml
- name: data_stream.type
external: ecs
- name: data_stream.dataset
external: ecs
- name: data_stream.namespace
external: ecs
- name: event.module
external: ecs
type: constant_keyword
value: <package_name>
- name: event.dataset
external: ecs
type: constant_keyword
value: <package_name>.<stream_name>
- name: '@timestamp'
external: ecs
Do not add other fields here. Only these routing constants and
belong in
.
candidates
Fields that hold a single value for every document in a data stream should use
. Beyond the routing constants in
, evaluate these:
| Field | Why |
|---|
| One value per data stream by definition |
| One value per package |
| Fixed per stream (/) |
| Fixed per stream |
| Set at deployment, constant within index |
| Package represents one vendor |
| Package represents one product |
When a
field is also an ECS field (e.g.,
), use
with the type override. This inherits the description from ECS and avoids manual duplication. Place the definition in the appropriate field file (
for most ECS fields,
for routing constants):
yaml
- name: observer.vendor
external: ecs
type: constant_keyword
value: Acme Corp
option: Because
stores the value once in index metadata, it does not need to appear in every document's
. Elasticsearch handles this automatically — no explicit
configuration is needed. This saves storage when the value is always the same.
Populate this file with every ECS field the pipeline sets. Use only
and
for each entry — no type, no description. The type is resolved from the ECS schema via
.
must be used whenever a field name exists in ECS (
wiki reference). This applies across field files —
,
, and any file that defines an ECS field. You may override properties (e.g.,
,
) while still using
— the description is inherited from ECS. Do not use
in
,
, or
— those files define non-ECS fields.
yaml
- name: event.kind
external: ecs
- name: event.category
external: ecs
- name: event.type
external: ecs
- name: event.outcome
external: ecs
- name: event.action
external: ecs
- name: source.ip
external: ecs
- name: source.port
external: ecs
- name: destination.ip
external: ecs
- name: user.name
external: ecs
- name: related.ip
external: ecs
- name: related.user
external: ecs
When attaching extra metadata to an ECS field (for example making a field a TSDB dimension or a constant_keyword with a fixed value), combine
with that metadata. The description is inherited from ECS. Place the definition in
(or
for routing constants):
yaml
- name: observer.vendor
external: ecs
type: constant_keyword
value: Acme Corp
Integration-specific custom (non-ECS) fields only. Use a nested
hierarchy for the vendor namespace:
yaml
- name: acme.firewall
type: group
fields:
- name: rule_id
type: keyword
- name: policy_name
type: keyword
- name: bytes_in
type: long
unit: byte
metric_type: gauge
Groups do not need to be declared as
— defining a
with nested
is sufficient. The object structure is implicit.
exception
is a core ECS object (
,
) designed for ad-hoc key-value metadata. Subkeys under
do
not require vendor namespacing — this is the one exception to the vendor-prefix rule.
Use
for simple keyword flags or integration-internal markers (e.g.,
labels.is_ioc_transform_source
). Use the vendor namespace for structured or nested data from an upstream source.
Flags vs structured data
Boolean flags and simple tags can live flat under the vendor group:
yaml
- name: acme.firewall
type: group
fields:
- name: is_encrypted
type: boolean
- name: policy_name
type: keyword
Structured data from the source should use sub-groups for logical hierarchy:
yaml
- name: acme.firewall
type: group
fields:
- name: rule
type: group
fields:
- name: id
type: keyword
- name: name
type: keyword
- name: action
type: keyword
Non-ECS fields populated by the Elastic Agent or Beats framework but not covered by ECS. Include only when the input type emits these fields. Typical fields:
,
,
,
,
,
,
.
See
references/root-and-core-fields.md
for full YAML samples.
Filebeat/Beats-specific fields not covered by ECS. Minimal form contains
and
. Some inputs also emit
or
sub-fields.
See
references/root-and-core-fields.md
for full YAML samples.
ECS field selection
Prefer ECS fields whenever semantics match. If no ECS field exists for the data, add it under the package namespace in
.
Categorization quick reference
| Field | Type | Notes |
|---|
| keyword | Highest-level classification. |
| keyword[] | Broad domain buckets — always an array. |
| keyword[] | Sub-buckets within category — always an array. |
| keyword | , , ; only set when meaningful. |
- : , , , , , , ,
- : , , , , , , , , , , , , , , , , , , ,
- : , , , , , , , , , , , , , , , , ,
Decision workflow:
- : for normal logs, for measurements, for snapshots, in
- : one or more values (array) for the broad domain
- : one or more values (array) for operation style
- : only when a clear success/failure/unknown applies; omit for informational/metric events
- If no allowed value fits, leave the field empty — do not invent values
Use
for source-specific verbs (
,
,
).
See
references/categorization-cheatsheet.md
for full worked examples.
Timestamp fields
ECS defines several timestamp fields with distinct semantics. Use them correctly:
| Field | When to use | Set by |
|---|
| The primary event timestamp. Parse from the source event data. Required. | Integration pipeline |
| When the event was first created or recorded by the source system, if different from . | Integration pipeline |
| When an activity or period began (e.g., session start, connection start). | Integration pipeline |
| When an activity or period ended (e.g., session end, connection close). | Integration pipeline |
| When the event was ingested into Elasticsearch. | Elasticsearch (outside the integration) |
must NEVER be set by an integration pipeline. It is managed automatically by Elasticsearch's final pipeline. Do not add a
processor for
.
When the source data contains multiple timestamps:
- Map the primary event timestamp to .
- If another timestamp represents when the event was first recorded/created, map it to .
- If timestamps represent the start or end of an activity, map them to and .
- If a timestamp does not match the semantics of any of the above, map it to a custom field under the vendor namespace with in .
Reusable fieldset nesting rules
Some ECS field sets must be nested under a parent entity — they are not valid at document root.
— must be nested under:
,
,
,
,
,
,
Root-level
fields are not recognized and will appear unmapped. Always set
on the
processor:
yaml
- geoip:
field: source.ip
target_field: source.geo
ignore_missing: true
(Autonomous System) — nested under:
,
,
,
When using
for geolocation, always also perform an ASN lookup using
and rename the raw output fields to ECS names. The
ASN processor outputs
and
, which must be renamed to
and
:
yaml
- geoip:
database_file: GeoLite2-ASN.mmdb
field: source.ip
target_field: source.as
properties:
- asn
- organization_name
ignore_missing: true
- rename:
field: source.as.asn
target_field: source.as.number
ignore_missing: true
- rename:
field: source.as.organization_name
target_field: source.as.organization.name
ignore_missing: true
See the
skill →
references/processor-cookbook.md
for the full geo+ASN pattern with both source and destination.
Nested (array-of-objects) ECS fields
Some ECS fields use
, meaning they hold an
array of objects where each object groups related sub-fields together. The pipeline must produce this structure — do
not flatten these into parallel scalar arrays.
ECS fields that use type:
| Field | Contains |
|---|
| , , , , |
| , |
threat.indicator.file.elf.sections
| , , , etc. |
threat.indicator.file.pe.sections
| , , , etc. |
| , , , etc. |
| , , , etc. |
Anti-pattern — parallel arrays (WRONG):
json
{
"email": {
"attachments": {
"file": {
"name": ["a.pdf", "b.pdf"],
"size": [1024, 2048]
}
}
}
}
This loses the association between each attachment's name and size. Queries cannot isolate individual objects.
Correct — array of objects:
json
{
"email": {
"attachments": [
{ "file": { "name": "a.pdf", "size": 1024 } },
{ "file": { "name": "b.pdf", "size": 2048 } }
]
}
}
declaration: declare only the parent
field with
. Child fields (
email.attachments.file.name
, etc.) inherit their types from the ECS schema — do not redeclare them individually.
yaml
- name: email.attachments
external: ecs
Pipeline construction: when source data delivers attachment metadata as separate parallel arrays (e.g., a comma-separated list of filenames and a separate list of sizes), use a
processor to zip them into an array of objects. See
→
references/painless-patterns.md
for array construction patterns and
references/processor-cookbook.md
→
Foreach semantics for iterating over array elements.
yaml
- script:
tag: build_email_attachments
description: Build email.attachments as array of nested objects from parallel source arrays.
lang: painless
if: ctx.json?.file_names instanceof List && ctx.json?.file_sizes instanceof List
source: |-
def names = ctx.json.file_names;
def sizes = ctx.json.file_sizes;
int len = Math.min(names.size(), sizes.size());
def attachments = new ArrayList(len);
for (int i = 0; i < len; i++) {
def attachment = new HashMap();
def file = new HashMap();
file.put('name', names.get(i));
file.put('size', sizes.get(i));
attachment.put('file', file);
attachments.add(attachment);
}
ctx.email = ctx.email ?: [:];
ctx.email.attachments = attachments;
When source data already delivers each attachment as a separate object (e.g., a JSON array of attachment objects), no zipping is needed — use
or
with
to place the array at
directly.
Custom field types
- for identifiers and exact-match strings
- for fixed values (dataset/module constants)
- , , for metrics and numeric values
- / for timestamps ( only when sub-millisecond precision is truly needed)
- for IP addresses
- for true/false (avoid string booleans in pipelines)
- for lat/lon coordinates
- with nested for logical structure — no need to separately declare intermediate nodes
- for arbitrary key/value blobs with unknown keys
- for arrays of objects requiring per-object query isolation (heavier than group)
- / for full-text content; add a sub-field via when aggregation is also needed
Useful properties on numeric fields:
(
or
),
(e.g.,
,
,
),
for low-cardinality TSDB fields.
See
references/mapping-type-matrix.md
for the full type reference.
Field naming conventions
| Rule | DO | DON'T |
|---|
| Use snake_case | , | , |
| Use lowercase | | |
| No asterisks in names | | (literal asterisk) |
| Use groups for hierarchy | as nested group | as flat dotted name |
Field names must never contain literal
characters. An asterisk in a field name is almost always a copy-paste error from documentation or wildcard patterns. Use a
with known subfields or
for dynamic keys instead.
Dotted field names vs nested groups
Both styles are valid in field files:
yaml
# Dotted (flat) — common for ECS fields in ecs.yml
- name: source.ip
external: ecs
# Nested group — common for custom fields
- name: acme.firewall
type: group
fields:
- name: rule_id
type: keyword
Pipeline expected output (
) always uses nested object form regardless of how the source data represented the field. A source
produces
{"host": {"name": "myhost"}}
in the output.
When source data contains literal dotted keys that Elasticsearch would otherwise expand, use
:
yaml
- dot_expander:
field: "*"
override: true
geo_point field handling
In pipeline test expected outputs,
fields appear as objects with
and
keys:
json
"source": {
"geo": {
"location": { "lat": 51.5142, "lon": -0.0931 },
"city_name": "London",
"country_iso_code": "GB"
}
}
These sub-fields do not need entries in
— they are part of the
type mapping. Only the
field (type
) needs to be in
for non-standard parent prefixes where
does not apply.
Common pipeline categorization patterns
Web access
yaml
- set:
field: event.kind
value: event
- append:
field: event.category
value: web
- append:
field: event.type
value: access
Outcome from HTTP status
yaml
- set:
field: event.outcome
value: success
if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code < 400"
- set:
field: event.outcome
value: failure
if: "ctx?.http?.response?.status_code != null && ctx.http.response.status_code >= 400"
Pipeline error fallback
yaml
on_failure:
- set:
field: event.kind
value: pipeline_error
Troubleshooting: "field X is undefined" for ECS fields
When tests report
field "destination.ip" is undefined
for standard ECS fields:
- Check exists at the package root
- Check
dependencies.ecs.reference
is set (use )
- Check the field is listed in with
Fix the root cause. Do not work around it by:
- Adding ECS fields with full type definitions to without
- Skipping and defining ECS field types/descriptions manually
Exception: Custom (non-ECS) fields reported as undefined must be defined in
.
Common failure patterns
- missing — all ECS fields reported undefined; create with
dependencies.ecs.reference
- outdated ECS version in — fields from newer ECS versions undefined; update reference to
- ECS field set in pipeline but missing from — field is undefined in test schema validation; add it to
- ECS field defined without — descriptions and types diverge from ECS; always use for ECS fields, with overrides as needed
- on non-numeric field — lint error
- at document root — unmapped; always nest under a parent entity
- or set as scalar — must use processor, not
- ECS field mapped as parallel arrays — , , and similar fields must be arrays of objects, not objects with parallel scalar arrays; see the Nested (array-of-objects) ECS fields section above
Validation loop
bash
elastic-package lint
elastic-package check
elastic-package test pipeline --data-streams <stream>
References
references/mapping-type-matrix.md
references/categorization-cheatsheet.md
references/root-and-core-fields.md
references/fieldset-links.md
- ECS field reference