Index Patterns and Data Views
Index patterns (renamed to "data views" in Kibana 8.x) define how Kibana connects to Elasticsearch indices. Every search, visualization, and dashboard depends on a properly configured data view.
What is a Data View?
A data view tells Kibana:
- Which indices to query (via a pattern like
logs-*) - Which field is the timestamp (for time-based analysis)
- What fields are available (names, types, formats)
Elasticsearch Indices Data View Kibana Apps
┌──────────────────┐
│ logs-2024-01-01 │
│ logs-2024-01-02 ├──→ logs-* ──→ Discover, Dashboards,
│ logs-2024-01-03 │ (@timestamp) Visualizations, Alerts
│ logs-2024-01-04 │
└──────────────────┘
Without a data view, Kibana has no way to access your Elasticsearch data.
Data Views vs Index Patterns
The terminology changed in Kibana 8.x:
| Kibana Version | Term | Location |
|---|---|---|
| 7.x and earlier | Index Pattern | Management → Index Patterns |
| 8.0+ | Data View | Stack Management → Data Views |
The functionality is the same. This guide uses "data view" but the concepts apply to both.
Creating a Data View
Via the UI
- Go to Stack Management → Kibana → Data Views
- Click "Create data view"
- Fill in the form:
Name: Web Server Logs
Index pattern: webserver-logs-*
Timestamp field: @timestamp
- Click "Save data view to Kibana"
Choosing an Index Pattern
The index pattern determines which Elasticsearch indices are included:
| Pattern | Matches | Use Case |
|---|---|---|
logs-* | logs-2024-01, logs-2024-02, etc. | All log indices |
metrics-* | metrics-cpu, metrics-memory, etc. | All metric indices |
filebeat-* | filebeat-8.11.0-2024.01.15, etc. | All Filebeat indices |
apm-* | apm-7.x, apm-8.x, etc. | All APM indices |
* | Everything | All indices (use carefully) |
logs-prod-* | logs-prod-web, logs-prod-api | Only production logs |
logs-2024.01.* | logs-2024.01.01 through .31 | January 2024 logs |
Wildcards in Patterns
* Matches everything
logs-* Matches any index starting with "logs-"
*-prod-* Matches any index containing "-prod-"
logs-*,-logs-debug-* Match logs-*, exclude logs-debug-*
Exclusion syntax: Use a comma followed by a minus sign to exclude indices:
Index pattern: logs-*,-logs-internal-*,-logs-debug-*
Matches: logs-web-*, logs-api-*, etc.
Excludes: logs-internal-*, logs-debug-*
Selecting a Timestamp Field
The timestamp field enables time-based filtering (the time picker):
Common timestamp fields:
- @timestamp (default for Beats, Logstash)
- timestamp (custom pipelines)
- order_date (business data)
- event.created (ECS format)
- event.ingested (when Elasticsearch received it)
No timestamp: Select "I don't want to use the time filter" for data without a time component (reference tables, configuration data).
Via API
Create data views programmatically:
# Create a data view
curl -X POST "localhost:5601/api/data_views/data_view" \
-H "kbn-xsrf: true" \
-H "Content-Type: application/json" \
-d '{
"data_view": {
"title": "filebeat-*",
"timeFieldName": "@timestamp",
"name": "Filebeat Logs"
}
}'
# Response
{
"data_view": {
"id": "abc123-def456",
"title": "filebeat-*",
"timeFieldName": "@timestamp",
"name": "Filebeat Logs",
"fields": { ... }
}
}
# List all data views
curl -X GET "localhost:5601/api/data_views" \
-H "kbn-xsrf: true"
# Get a specific data view
curl -X GET "localhost:5601/api/data_views/data_view/abc123-def456" \
-H "kbn-xsrf: true"
# Delete a data view
curl -X DELETE "localhost:5601/api/data_views/data_view/abc123-def456" \
-H "kbn-xsrf: true"
Understanding Fields
Field Types
Each field in a data view has a type determined by its Elasticsearch mapping:
| Type | Icon | Description | Example Fields |
|---|---|---|---|
| keyword | 🔤 | Exact values, aggregatable | status.keyword, category |
| text | t | Full-text searchable | message, description |
| long/integer | # | Whole numbers | response, bytes |
| float/double | # | Decimal numbers | price, cpu_percent |
| date | 📅 | Timestamps | @timestamp, order_date |
| boolean | ⊘ | true/false | is_active, has_error |
| ip | 🌐 | IP addresses | clientip, source.ip |
| geo_point | 📍 | Latitude/longitude | location, geoip.location |
| nested | {} | Array of objects | products, tags |
| object | {} | JSON object | user, event |
Field Properties
| Property | Meaning |
|---|---|
| Searchable | Can be queried with KQL/Lucene |
| Aggregatable | Can be used in visualizations (terms, histograms) |
| Scripted | Calculated at query time (not stored in index) |
Common confusion: text fields are searchable but NOT aggregatable. Use the .keyword sub-field for aggregations.
Field: category (text) → Searchable, NOT aggregatable
Field: category.keyword → Searchable AND aggregatable
Viewing Field Details
- Go to Stack Management → Data Views
- Click a data view
- See the full field list with:
- Field name
- Type
- Format
- Searchable/Aggregatable status
- Popularity (usage count)
Field Formatting
Customize how field values display in Kibana.
Setting a Field Format
- Open data view → click field name → "Edit"
- Choose a format:
| Format | Description | Example |
|---|---|---|
| String | Default text | "hello world" |
| Number | Numeric formatting | 1,234,567 |
| Bytes | File/data sizes | 1.5 GB |
| Currency | Money values | $1,234.56 |
| Date | Date/time formatting | Jan 15, 2024 |
| Duration | Time spans | 2h 15m 30s |
| Percent | Percentage | 85.5% |
| URL | Clickable links | https://example.com |
| Color | Color-coded values | Red for errors |
| Truncated string | Shortened text | "This is a lo..." |
Number Format Examples
Field: taxful_total_price
Format: Number
Pattern: $0,0.00
Result: 1234.5 → $1,234.50
Field: bytes
Format: Bytes
Pattern: 0.0 b
Result: 1536000 → 1.5 MB
URL Format
Turn field values into clickable links:
Field: order_id
Format: URL
Template: https://orders.example.com/{{value}}
Result: "12345" → clickable link to https://orders.example.com/12345
Field: username
Format: URL
Type: Image
Template: https://avatars.example.com/{{value}}.png
Result: Shows user avatar image inline
Color Format
Apply colors based on value ranges:
Field: response
Format: Color
Rules:
- Range 200-299: Background green, text white
- Range 300-399: Background yellow, text black
- Range 400-499: Background orange, text white
- Range 500-599: Background red, text white
Scripted Fields
Scripted fields compute values at query time using Painless (Elasticsearch's scripting language).
Creating a Scripted Field
- Open data view → "Scripted fields" tab → "Add scripted field"
- Configure:
Name: price_with_tax
Language: painless
Type: number
Format: Number ($0,0.00)
Script: doc['price'].value * 1.1
Common Scripted Field Examples
Calculate a derived value:
// Full name from first and last
Name: full_name
Type: string
Script:
doc['first_name.keyword'].value + ' ' + doc['last_name.keyword'].value
Extract hour from timestamp:
// Hour of day (0-23) for time-of-day analysis
Name: hour_of_day
Type: number
Script:
doc['@timestamp'].value.getHour()
Categorize numeric values:
// Price tier
Name: price_tier
Type: string
Script:
if (doc['price'].value < 25) return 'Budget';
if (doc['price'].value < 100) return 'Standard';
return 'Premium';
Calculate percentage:
// Error rate
Name: error_rate_pct
Type: number
Script:
(doc['error_count'].value / doc['total_count'].value) * 100
Scripted Field Limitations
❌ Slow on large datasets (computed per document at query time)
❌ Cannot be used in filters (only in visualizations and tables)
❌ No access to _source (only doc values)
❌ May break if underlying fields change
✅ Good for simple calculations
✅ Useful for prototyping before creating an ingest pipeline
✅ Works without reindexing data
Better alternatives:
- Runtime fields (Kibana 8.x) - more flexible, better performance
- Ingest pipeline - compute at index time, stored in document
- Transform - pre-aggregate data into summary indices
Runtime Fields
Runtime fields are the modern replacement for scripted fields in Kibana 8.x.
Creating a Runtime Field
- Open data view → "Add field" (or click the
+icon) - Toggle "Set value" on
- Choose type and write the script:
Name: response_category
Type: Keyword
Script (Painless):
if (doc['response'].value >= 200 && doc['response'].value < 300) {
emit('success');
} else if (doc['response'].value >= 400 && doc['response'].value < 500) {
emit('client_error');
} else if (doc['response'].value >= 500) {
emit('server_error');
} else {
emit('other');
}
Runtime Field Advantages over Scripted Fields
| Feature | Scripted Fields | Runtime Fields |
|---|---|---|
| Filterable | No | Yes |
| Usable in KQL | No | Yes |
| Performance | Slower | Optimized |
| Syntax | Older API | Modern emit() |
| Recommended | No (legacy) | Yes |
Runtime Field Examples
Mask sensitive data:
Name: masked_email
Type: Keyword
Script:
def email = doc['email.keyword'].value;
def parts = email.splitOnToken('@');
emit(parts[0].substring(0, 2) + '***@' + parts[1]);
// "john.doe@example.com" → "jo***@example.com"
Extract URL path segments:
Name: api_version
Type: Keyword
Script:
def path = doc['url.path.keyword'].value;
if (path.startsWith('/api/v')) {
emit(path.substring(5, 7));
} else {
emit('unknown');
}
// "/api/v2/users" → "v2"
Managing Multiple Data Views
Data View Strategy
Organize data views by purpose:
Production Monitoring:
├── logs-prod-* (all production logs)
├── metrics-prod-* (all production metrics)
└── apm-prod-* (production APM data)
Development:
├── logs-dev-* (development logs)
└── metrics-dev-* (development metrics)
Business Analytics:
├── orders-* (all order data)
├── customers-* (customer records)
└── products-* (product catalog)
Security:
├── auditbeat-* (audit logs)
├── packetbeat-* (network data)
└── winlogbeat-* (Windows events)
Cross-Cluster Data Views
If you have multiple Elasticsearch clusters connected via cross-cluster search:
Index pattern: cluster_name:logs-*
Example:
us-east:logs-* (logs from US East cluster)
eu-west:logs-* (logs from EU West cluster)
*:logs-* (logs from all clusters)
Setting a Default Data View
- Go to Stack Management → Advanced Settings
- Search for
defaultIndex - Select the data view ID to use as default
- Click "Save"
The default data view is pre-selected when opening Discover or creating visualizations.
Refreshing Data Views
When index mappings change (new fields added, types updated), refresh the data view:
Manual Refresh
- Go to Stack Management → Data Views
- Open the data view
- Click the refresh icon (🔄) in the top-right
When to Refresh
Refresh when:
✅ New fields appear in your data
✅ Field types change (rare, usually a reindex)
✅ New indices matching the pattern are created
✅ Fields show as "unknown" type
No refresh needed:
❌ New documents added to existing indices
❌ Time range changes
❌ Query changes
Data View Spaces
Data views are scoped to Kibana Spaces:
Default Space:
├── logs-*
├── metrics-*
└── apm-*
Marketing Space:
├── analytics-*
└── campaigns-*
Security Space:
├── auditbeat-*
└── security-*
To share a data view across spaces, create it in each space or use the Saved Objects copy feature:
- Stack Management → Saved Objects
- Find the data view
- Click "Copy to space"
- Select target spaces
Practical Examples
Example 1: Multi-Environment Setup
# Create data views for each environment via API
for ENV in prod staging dev; do
curl -X POST "localhost:5601/api/data_views/data_view" \
-H "kbn-xsrf: true" \
-H "Content-Type: application/json" \
-d "{
\"data_view\": {
\"title\": \"logs-${ENV}-*\",
\"timeFieldName\": \"@timestamp\",
\"name\": \"Logs (${ENV})\"
}
}"
done
Example 2: Data View with Field Formatting
After creating the data view, set up useful field formats:
Field: response → Color format (green 2xx, red 5xx)
Field: bytes → Bytes format (auto KB/MB/GB)
Field: url.full → URL format (clickable link)
Field: duration_ms → Duration format (humanized)
Field: price → Number format ($0,0.00)
Example 3: Runtime Field for SLA Tracking
// Create a runtime field: sla_status
Name: sla_status
Type: Keyword
Script:
def responseTime = doc['response_time_ms'].value;
if (responseTime <= 200) {
emit('within_sla');
} else if (responseTime <= 500) {
emit('warning');
} else {
emit('sla_breach');
}
Now use this in visualizations:
- Pie chart: SLA compliance breakdown
- Metric: Percentage within SLA
- Alert: Trigger when
sla_breachcount exceeds threshold
Common Issues
"No matching indices"
Cause: No Elasticsearch indices match the pattern.
Fix:
- Verify indices exist:
GET _cat/indices/logs-* - Check for typos in the pattern
- Confirm the index name format matches your pattern
"Field not found" in queries
Cause: Field exists in some indices but not others matched by the pattern.
Fix:
- Refresh the data view
- Check which indices actually contain the field
- Narrow the index pattern if needed
Conflicting field types
Cause: Same field name has different types across indices (e.g., status is keyword in one index and long in another).
Fix:
- Kibana shows a conflict warning icon
- Choose the correct type for your use case
- Better: fix the mapping inconsistency at the source with an index template
# Check field mappings across indices
curl "localhost:9200/logs-*/_mapping/field/status"
Too many fields
Cause: Data view matches indices with thousands of fields, slowing down Kibana.
Fix:
- Narrow the index pattern to fewer indices
- Use
metaFieldssetting to limit overhead - Consider field filtering in the data view
# kibana.yml - limit fields
# (use with caution, may hide needed fields)
xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled: true
Summary
In this chapter, you learned:
- ✅ What data views are and why they matter
- ✅ Creating data views with index patterns and timestamp fields
- ✅ Understanding field types and their implications
- ✅ Formatting fields for better readability
- ✅ Using scripted fields and runtime fields for computed values
- ✅ Managing data views across environments and spaces
- ✅ Programmatic data view creation via API
- ✅ Troubleshooting common data view issues
Next: Building drag-and-drop visualizations with Lens!