Transformers let you run a SQL query against each batch of records before Logpush delivers them to your destination. Use them to filter records you do not want to store, reshape fields to match a downstream schema, redact sensitive values, compute new fields, or add static metadata.
You write the logic as a single SQL query, attach it to a Logpush job, and Cloudflare runs it on every batch. The FROM clause names the Logpush dataset (for example, http_requests or audit_logs_v2) and field names come from that dataset's schema.
- SQL-based transforms - a single-statement SQL query per Logpush job.
- Per-record execution - runs on each NDJSON record before Cloudflare delivers the batch.
- Advanced filtering and reshaping - drop, rename, redact, compute, or tag fields.
- Attach and detach without redeploying - manage transformers from the Cloudflare dashboard or the API.
- Version history - every save creates a new version; older versions remain viewable.
Before you begin, you need:
- A Logpush job that uses the
ndjsonoutput format. Transformers are only available for NDJSON jobs, and are supported for both account-scoped and zone-scoped datasets. - An API token with the
Logs Writepermission for the account. - Familiarity with the dataset whose records you plan to transform. Your SQL references its field names directly.
You can create, preview, attach, and manage transformers through the Cloudflare dashboard or the API.
Transformer Studio is the workspace that includes a SQL editor where you write, preview, and manage Transformers. Open it from the Logpush page in the Cloudflare dashboard.
Go to Logpush ↗From Studio you can:
- Create a new transformer by writing a SQL query against a Logpush dataset.
- Preview a transformer against a canned sample record for the target dataset before saving.
- Attach a transformer to any eligible Logpush job on the account or zone. Only NDJSON jobs matching the transformer's dataset appear as available attach targets. CSV jobs are not shown.
- Detach a transformer from a job.
- Save a new version each time you edit and save the SQL. Older versions remain available to view.
- Rename a transformer or update its description.
- Delete a transformer. A transformer cannot be deleted while any Logpush job references it.
Every transformer action is available through the Cloudflare API. To authenticate, use an API token with the Logs Write permission.
| Operation | Method | Endpoint |
|---|---|---|
| List transformers | GET |
accounts/:account_id/logpush/transformers |
| Create a transformer | POST |
accounts/:account_id/logpush/transformers |
| Preview a transformer | POST |
accounts/:account_id/logpush/transformers/preview |
| Get a transformer | GET |
accounts/:account_id/logpush/transformers/:id |
| Download SQL | GET |
accounts/:account_id/logpush/transformers/:id/content |
| List versions | GET |
accounts/:account_id/logpush/transformers/:id/versions |
| Update a transformer | PUT |
accounts/:account_id/logpush/transformers/:id |
| Delete a transformer | DELETE |
accounts/:account_id/logpush/transformers/:id |
To attach or detach a transformer from a job, set transformer_id on the Logpush job. Refer to Logpush job setup for job endpoints.
A transformer is a single SQL query. The Logpush dataset is the source table; the query output becomes the delivered record.
SELECT ClientIP, RayID, EdgeResponseStatus
FROM http_requests
WHERE EdgeResponseStatus >= 400The FROM table name must match the dataset of the Logpush job the transformer is attached to. If it does not, attachment fails.
Records that do not match the WHERE clause are dropped from the output.
Transformers use the same SQL dialect as Cloudflare Pipelines. The following operations are supported:
- Projection -
SELECTspecific fields, rename withAS, compute new fields with expressions. - Filtering -
WHEREclauses with the standard comparison, boolean, and null-check operators. - CTEs -
WITH ... AS (...)common table expressions. UNNEST- expand array or list fields into rows.- JSON access - the
->operator returns a JSON object;->>returns a string. For example,RequestHeaders ->> 'Host'. - Nested output -
named_struct('key', value, ...)builds a nested JSON object. - Array output -
[value1, value2]builds a JSON array. - Scalar functions - standard SQL functions including
UPPER,LOWER,COALESCE,CAST,extract, andto_timestamp.
- Joins
- Subqueries
- Aggregation (
GROUP BY,HAVING,COUNT,SUM) - Window functions
ORDER BY- Multiple statements - one query per transformer
Every SQL query is validated against the target dataset's schema before it is saved. Unknown fields, wrong types, invalid syntax, unknown datasets, and unsupported operations are rejected at upload time.
In the dashboard, validation errors appear inline in the editor with line and column numbers. Through the API, they are returned in the errors array of the response.
| Limit | Value |
|---|---|
| SQL query size | 10 KB |
| Transformer name length | 255 bytes |
| Transformer description length | 4,096 bytes |
| Filesystem access from a query | None |
| Network access from a query | None |
| Batch chunk size | 1,000 rows |
The examples below apply every capability from Key features in a single query. Each keeps a subset of records, reshapes the survivors, and drops fields the downstream pipeline does not need.
This transformer keeps only update actions from the audit trail and reshapes the surviving records for downstream delivery. Specifically, it:
- Excludes every record whose
ActionTypeis notupdate. - Converts
ActionTimestampfrom RFC3339 into a Unix epoch integer, renamedunix_ts. - Uppercases
ActionTypeand renames itaction_type. - Adds a hardcoded
providerfield with the valueCloudflare. - Groups
ActorType,ActorEmail, andActorIPAddressinto a nestedactorobject. - Derives a boolean
is_zoneflag fromResourceType = 'zone'. - Builds a
resource_metaarray fromResourceTypeandResourceID. - Drops
ActorID,AccountID, andActorContextby omission from theSELECT.
Input record from the audit_logs_v2 dataset:
{
"ActionType": "update",
"ActorEmail": "user@example.com",
"ActorID": "a1b2c3d4",
"ActorIPAddress": "203.0.113.42",
"ActorType": "user",
"ActionTimestamp": "2026-05-21T15:00:00Z",
"AccountID": "90796717",
"ResourceID": "r1s2t3u4",
"ResourceType": "zone",
"ActorContext": "dashboard"
}Transformer:
SELECT
extract(epoch FROM to_timestamp(ActionTimestamp)) AS unix_ts,
UPPER(ActionType) AS action_type,
'Cloudflare' AS provider,
named_struct(
'type', ActorType,
'email', ActorEmail,
'ip', ActorIPAddress
) AS actor,
ResourceType = 'zone' AS is_zone,
[ResourceType, ResourceID] AS resource_meta
FROM audit_logs_v2
WHERE ActionType = 'update'Delivered record:
{
"action_type": "UPDATE",
"actor": {
"email": "user@example.com",
"ip": "203.0.113.42",
"type": "user"
},
"is_zone": true,
"provider": "Cloudflare",
"resource_meta": ["zone", "r1s2t3u4"],
"unix_ts": 1779375600
}This transformer keeps all HTTP traffic except health checks, metrics scrapers, and internal-facing hostnames. This is a common pattern for teams that want the full log stream, minus predictable noise. Specifically, it:
- Excludes every request to
internal.example.comandhealth.example.com. - Excludes every request to paths starting with
/healthzor/metrics. - Converts
EdgeStartTimestampfrom RFC3339 into a Unix epoch integer, renamedunix_ts. - Uppercases
ClientRequestMethodand renames itmethod. - Adds a hardcoded
providerfield with the valueCloudflare. - Groups
ClientRequestHost,ClientRequestPath, andClientRequestMethodinto a nestedrequestobject. - Derives a boolean
is_server_errorflag fromEdgeResponseStatus >= 500. - Builds a
request_metaarray fromClientRequestHostandClientRequestPath. - Drops
ClientIP,ClientRequestUserAgent,RayID,OriginResponseTime, andWAFActionby omission from theSELECT.
Input record from the http_requests dataset:
{
"ClientIP": "203.0.113.42",
"ClientRequestHost": "example.com",
"ClientRequestMethod": "POST",
"ClientRequestPath": "/api/checkout",
"ClientRequestUserAgent": "curl/7.85.0",
"EdgeResponseStatus": 502,
"EdgeStartTimestamp": "2026-05-21T15:00:00Z",
"RayID": "8e2a1c60ef9e1c9a",
"OriginResponseTime": 3200000000,
"WAFAction": "unknown"
}This example assumes EdgeStartTimestamp is delivered as an RFC3339 string. If your job delivers timestamps as Unix nanoseconds, drop the to_timestamp() wrapper and divide by 1e9 instead.
Transformer:
SELECT
extract(epoch FROM to_timestamp(EdgeStartTimestamp)) AS unix_ts,
UPPER(ClientRequestMethod) AS method,
'Cloudflare' AS provider,
named_struct(
'host', ClientRequestHost,
'path', ClientRequestPath,
'method', ClientRequestMethod
) AS request,
EdgeResponseStatus >= 500 AS is_server_error,
[ClientRequestHost, ClientRequestPath] AS request_meta
FROM http_requests
WHERE ClientRequestHost NOT IN ('internal.example.com', 'health.example.com')
AND ClientRequestPath NOT LIKE '/healthz%'
AND ClientRequestPath NOT LIKE '/metrics%'Delivered record:
{
"is_server_error": true,
"method": "POST",
"provider": "Cloudflare",
"request": {
"host": "example.com",
"method": "POST",
"path": "/api/checkout"
},
"request_meta": ["example.com", "/api/checkout"],
"unix_ts": 1779375600
}| HTTP | Message | Cause |
|---|---|---|
403 |
transformer feature is not available for this account |
Your account does not have access to Transformers. Contact your Cloudflare Account Executive. |
400 |
missing required field: name |
Add a name field to the request body. |
400 |
missing required field: code |
Add a non-empty code field with your SQL query. |
400 |
Schema validation error (unknown column, invalid syntax) | The SQL references a field that does not exist, uses unsupported syntax, or has a type mismatch. Fix the query and retry. |
413 |
(request entity too large) | The SQL query exceeds 10 KB. Shorten the query. |
400 |
transformer N not found for this account |
The transformer ID does not exist, or belongs to a different account. |
400 |
transformer N dataset "X" does not match job dataset "Y" |
The transformer's FROM table does not match the job's dataset. |
If a transformer fails while processing a batch, the batch fails: nothing is delivered for it, an error is recorded on the Logpush job, and Logpush retries the batch on its normal schedule. There is no automatic raw-log fallback.
If failures continue, records in the affected batches are eventually dropped and cannot be recovered.
The last error appears on the job's last_error field. Common causes:
- The output exceeded the size limit. Reduce output per record or drop more records with
WHERE. - An internal Cloudflare error occurred. Contact Cloudflare Support with the job ID and timestamp.
To debug, open the transformer in Transformer Studio and use the Run button to preview it against a sample record. Validation and execution logic are the same, so problems visible in production usually reproduce in preview.
- Logpush datasets - the fields your SQL queries reference
- Logpush job setup - creating and managing Logpush jobs
- Log fields reference - full field descriptions across datasets
- Filters - simpler filtering without SQL