How to Use Hog Functions for Data Transformations and CDP in PostHog
Hog Functions provide a unified REST API to run custom Hog or TypeScript code on events, enabling real-time transformations, external destinations, and site apps without deploying separate infrastructure.
PostHog's Hog Functions system, implemented in the PostHog/posthog repository, offers a serverless approach to Customer Data Platform (CDP) operations. This architecture allows teams to mutate, enrich, and forward event data using either Hog or TypeScript code executed directly within the PostHog pipeline through three distinct use cases: Destinations, Transformations, and Site Apps.
Understanding the Hog Functions Architecture
Hog Functions consolidate CDP logic into HogFunction model objects managed via the REST API at /api/projects/@current/hog_functions/. The system handles compilation, validation, and runtime execution through several specialized components.
Core implementation files:
posthog/models/hog_functions/hog_function.py– Defines the database model, handles file-system synchronization, and manages status polling through theHogFunctionclass.posthog/api/hog_function.py– Contains the DRF viewset andHogFunctionSerializer, which validates code size, compiles Hog viacompile_hog, and transpiles TypeScript viaget_transpiled_function.posthog/cdp/validation.py– Provides thecompile_hogutility and template bytecode generation.posthog/plugins/plugin_server_api.py– Manages communication with the plugin server for reloading functions and testing invocations.
When a function is saved, the hog_function_saved signal (lines 49-54 in the model file) automatically reloads the function on the plugin server for relevant types, ensuring zero-downtime updates.
Creating a Transformation Function
Transformations mutate or enrich events before they continue through the pipeline. To create one, send a POST request to the Hog Functions endpoint.
Minimal required payload:
{
"type": "transformation",
"name": "Add GeoIP data",
"hog": "function addGeoIP(event) { /* Hog code */ }",
"enabled": true,
"execution_order": 1,
"inputs_schema": [],
"filters": {}
}
Validation and compilation process:
- The
HogFunctionSerializer.validate_typemethod (lines 55-68 inposthog/api/hog_function.py) ensures the type is"transformation". - If
execution_orderis omitted, the serializer calls_get_highest_execution_order(lines 86-94) to assign the next highest integer. - During validation, the serializer invokes
compile_hogfromposthog/cdp/validation.pyto compile the Hog code. - The model's
savemethod triggerscompile_filters_bytecode(lines 37-44 inposthog/models/hog_functions/hog_function.py) for non-JavaScript sources.
Using Built-in Templates
PostHog ships with pre-defined templates that can be instantiated without writing code from scratch. The GeoIP transformation, for example, automatically enriches events with location data.
To create a function from a template:
{
"template_id": "template-geoip",
"type": "transformation",
"enabled": true
}
The serializer loads the template via HogFunctionTemplate.get_template and copies its code, icon, and input schema. New teams automatically receive default transformations through the enabled_default_hog_functions_for_new_team signal (lines 111-150 in the model file).
Testing Functions Before Production
Always test transformations before enabling them on live traffic. Use the invocations endpoint to simulate execution.
API endpoint:
POST /api/projects/@current/hog_functions/<function_id>/invocations/
Test payload structure:
{
"configuration": {
"type": "transformation",
"hog": "function addGeoIP(event) { /* … */ }",
"enabled": false,
"filters": {}
},
"globals": {
"event": { "event": "$pageview", "properties": {} }
},
"clickhouse_event": {
"event": "$pageview",
"properties": {}
}
}
The HogFunctionViewSet.invocations method forwards the payload to the plugin server via create_hog_invocation_test (lines 51-68 in the API file). The response includes status, execution logs, and an optional invocation_id for debugging.
Managing Execution Order for Multiple Transformations
When multiple transformation functions are active, the execution_order field determines their sequence. Lower numbers execute first.
To bulk update the order of several functions:
Endpoint:
PATCH /api/projects/@current/hog_functions/rearrange/
Request body:
{
"orders": {
"c8a2f7e3-5c4b-4d2a-9b8e-1f5d2a6b9c3d": 1,
"e7d3b9a1-2f6c-4b8d-8e5a-0c9e1a2d3f4b": 2
}
}
The viewset validates that all IDs belong to the requesting team, updates the execution_order fields, writes an activity log entry, and returns the newly ordered list.
Enabling Historic Backfills for Destinations
For destination functions (not transformations), you can replay historic events by attaching a Batch Export backfill workflow.
Endpoint:
POST /api/projects/@current/hog_functions/<function_id>/enable_backfills/
This endpoint constructs a Batch Export configuration, validates the "backfill-workflows-destination" feature flag, and stores the resulting export ID on the function object (lines 84-112 in posthog/api/hog_function.py).
Complete Implementation Example
The following cURL commands demonstrate the full lifecycle of a transformation function:
1. Create the transformation:
curl -X POST https://app.posthog.com/api/projects/@current/hog_functions/ \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"type":"transformation",
"name":"Upper-case event name",
"hog":"function uppercase(event) { event.event = event.event.toUpperCase(); return event; }",
"enabled":false,
"execution_order":1
}'
2. Test with sample data:
curl -X POST https://app.posthog.com/api/projects/@current/hog_functions/<id>/invocations/ \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"configuration": {
"type":"transformation",
"hog":"function uppercase(event){event.event=event.event.toUpperCase();return event;}"
},
"globals": {"event": {"event":"$pageview","properties":{}}}
}'
3. Enable after successful testing:
curl -X PATCH https://app.posthog.com/api/projects/@current/hog_functions/<id>/ \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"enabled":true}'
Summary
- Hog Functions unify transformations, destinations, and site apps into a single serverless system accessed via
/api/projects/@current/hog_functions/. - The
HogFunctionSerializerautomatically validates type constraints, assignsexecution_order, and compiles Hog code usingcompile_hogfromposthog/cdp/validation.py. - Test functions using the
/invocations/endpoint and thecreate_hog_invocation_testutility before enabling them in production. - Reorder chained transformations via the
/rearrange/endpoint to control mutation sequence. - Backfill historic data for destinations using the
/enable_backfills/endpoint, which creates Batch Export configurations. - All state changes trigger the
hog_function_savedsignal to reload the plugin server without manual intervention.
Frequently Asked Questions
What is the difference between a transformation and a destination in Hog Functions?
A transformation mutates or enriches the event object itself and passes the modified event back into the pipeline for further processing. A destination forwards a copy of the event to an external service (like a webhook or data warehouse) without altering the original event flow. Transformations use the execution_order field to sequence multiple mutations, while destinations support historic backfills via the /enable_backfills/ endpoint.
How do I debug a Hog Function that is not processing events?
Use the /invocations/ test endpoint to simulate event processing and inspect the returned logs array. Check the function's status in the HogFunction model and verify that the hog_function_saved signal fired correctly to reload the plugin server. For runtime issues, examine the compile_hog output in posthog/cdp/validation.py to ensure your code compiles without errors.
Can I write Hog Functions in TypeScript instead of Hog?
Yes. For site apps and specific use cases, the system supports TypeScript through the get_transpiled_function method in posthog/cdp/site_functions.py. The HogFunctionSerializer detects the language and routes the code accordingly, though transformations and destinations typically use Hog for execution within the plugin server.
What happens if I don't specify an execution_order for a transformation?
The HogFunctionSerializer automatically assigns the next highest available integer via the _get_highest_execution_order method (lines 86-94 in posthog/api/hog_function.py). This ensures the new transformation runs after all existing ones by default. To prioritize a transformation earlier in the chain, explicitly set a lower integer value or use the /rearrange/ endpoint to reorder multiple functions atomically.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →