liquid_posthog
PostHog destination for
liquid_analytics — product analytics, session replay,
and feature flags, with an open-source/self-host option.
Install
dependencies:
liquid_analytics: ^0.2.0
liquid_posthog: ^0.2.0
Use
Let the sink configure PostHog for you by passing an apiKey:
final liquid = Liquid(
sinks: [
PostHogSink(
apiKey: 'phc_xxx',
host: 'https://eu.i.posthog.com',
configure: (config) => config
..debug = true
..captureApplicationLifecycleEvents = true,
),
],
consent: ConsentPolicy(
requireOptIn: true,
map: {ConsentCategory.analytics: ['posthog']},
),
);
await liquid.init();
liquid.track('checkout_started', {
'cart_value': 42.0,
'is_gift': true, // real booleans are preserved
'items': ['sku_1', 'sku_2'], // nested lists/maps are supported
});
liquid.consent.grant(ConsentCategory.analytics);
If PostHog is already initialized natively or via Posthog().setup(...), omit
apiKey and the sink will use the existing instance.
Typed events (recommended)
The string API above is the quick path. A typed event keeps the name and property keys in one place, and PostHog preserves the nested shapes such an event naturally produces:
class CheckoutStarted extends LiquidEvent {
const CheckoutStarted({required this.cartValue, required this.items});
final double cartValue;
final List<String> items;
@override
String get name => 'checkout_started';
@override
Map<String, Object?> get properties => {
'cart_value': cartValue,
'items': items, // lists and nested maps reach PostHog intact
};
}
liquid.log(const CheckoutStarted(cartValue: 42, items: ['sku_1', 'sku_2']));
Override category to route an event to a different consent bucket:
class PromoTapped extends LiquidEvent {
const PromoTapped({required this.campaign});
final String campaign;
@override
String get name => 'promo_tapped';
@override
ConsentCategory get category => ConsentCategory.marketing;
@override
Map<String, Object?> get properties => {'campaign': campaign};
}
No build step is involved — these are plain classes. To generate them from a
YAML schema instead, see
liquid_codegen.
Message mapping
Unlike Firebase, PostHog models liquid's full surface natively:
| liquid message | PostHog call |
|---|---|
track |
capture |
screen |
screen |
identify |
identify |
group |
group |
alias |
alias |
reset |
reset |
PostHog's group uses a (groupType, groupKey) model. liquid's single-id
group('acme') maps groupKey = 'acme' with the type set by defaultGroupType
('company' by default):
PostHogSink(apiKey: '…', defaultGroupType: 'organization');
Property handling
PostHog accepts far more than Firebase — event names are unconstrained and
properties may be nested objects, lists, booleans and numbers. PostHogPropertyMapper
therefore only:
- drops
nullvalues (PostHog's API is non-nullable), and - converts the couple of Dart types the platform channel cannot serialize —
DateTime(to ISO-8601) andEnum(to.name) — recursively through nested maps and lists.
This is a deliberate contrast with liquid_firebase, whose mapper must flatten
and sanitize aggressively. Each adapter owns the constraints of its provider so
your app code never has to.
Testing
PostHogPropertyMapper is a pure transform and is unit-tested without a live
project. For end-to-end checks, run against a PostHog project (cloud or
self-hosted) and watch the activity feed.
Libraries
- liquid_posthog
- PostHog destination for liquid_analytics.