Skip to main content
Ask your AI

Piwik PRO

Web Source code Package

Piwik PRO is a European, privacy-focused web analytics and marketing platform. The destination sends events to Piwik PRO via the _paq command queue: page views, custom events, goals, site search, links, content tracking, ecommerce and custom dimensions. With loadScript: true it also loads the Piwik PRO JavaScript tracking client from your account. For server-side delivery with the same mapping, see the Piwik PRO server destination.

Where this fits

Piwik PRO is a web destination in the walkerOS flow:

Installation​

npm install @walkeros/web-destination-piwikpro
import { startFlow } from '@walkeros/collector';
import { destinationPiwikPro } from '@walkeros/web-destination-piwikpro';

await startFlow({
  destinations: {
    piwikpro: {
      code: destinationPiwikPro,
      config: {
        loadScript: true,
        settings: {
          appId: 'XXX-XXX-XXX-XXX-XXX',
          url: 'https://your_account_name.piwik.pro/',
        },
      },
    },
  },
});

Configuration​

This destination uses the standard destination config wrapper (consent, data, env, id, ...). For the shared fields see destination configuration. Package-specific fields live under config.settings and are listed below.

Settings​

PropertyTypeDescriptionMore
appIdstringID of the Piwik PRO site (like XXX-XXX-XXX-XXX-XXX). Required when loadScript is true
urlstringURL of your Piwik PRO account, normalized to one trailing slash (like https://your_account_name.piwik.pro/). Required when loadScript is true
linkTrackingbooleanAutomatic outlink and download tracking, enabled right after the first hit this destination sends. Default: true
identifiedboolean | objectIdentified or anonymous tracking. true (default) identifies every hit, false switches the tracker to anonymous mode without cookies (disableCookies, deleteCookies, setUserIsAnonymous) at init. A consent object like { marketing: true } identifies only while that consent is granted and calls enableCookies and deanonymizeUser once it is. Do not repeat these states in config.consent: a destination needs only one of its config.consent states granted, so next to another state the repeated one lets events through on its own, and as the only state it queues every event until granted, so no anonymous hit is sent.
customDimensionsRecord<string, Mapping.Value>Custom dimensions applied to every hit, keyed by bare dimension id (like { "1": "data.size" }). Rule-level customDimensions win per key.

Mapping​

Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

PropertyTypeDescriptionMore
goalIdstring | numberPiwik PRO goal id (UUID or legacy integer). Adds a second hit, trackGoal.
goalValueMapping.Value
customDimensionsRecord<string, Mapping.Value>Custom dimensions for this rule, keyed by bare dimension id (like { "1": "data.size" }). Wins per key over the destination customDimensions.

Examples

Add to cart

A product add calls Piwik PRO ecommerceAddToCart with the added product and the currency code.

Event
{
  "name": "product add",
  "data": {
    "id": "ers",
    "name": "Everyday Ruck Snack",
    "color": "black",
    "size": "l",
    "price": 420
  },
  "context": {
    "shopping": [
      "intent",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [],
  "consent": {
    "functional": true
  },
  "id": "83e860384d50953f",
  "trigger": "click",
  "entity": "product",
  "action": "add",
  "timestamp": 1700000000000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "name": "ecommerceAddToCart",
  "data": {
    "set": [
      {
        "set": [
          {
            "map": {
              "sku": "data.id",
              "name": "data.name",
              "price": "data.price",
              "quantity": {
                "key": "data.quantity",
                "value": 1
              },
              "variant": {
                "key": "data.color"
              },
              "customDimensions": {
                "map": {
                  "1": "data.size"
                }
              }
            }
          }
        ]
      },
      {
        "map": {
          "currencyCode": {
            "key": "data.currency",
            "value": "EUR"
          }
        }
      }
    ]
  }
}
Out
window._paq.push([
  "ecommerceAddToCart",
  [
    {
      "sku": "ers",
      "name": "Everyday Ruck Snack",
      "price": 420,
      "quantity": 1,
      "variant": "black",
      "customDimensions": {
        "1": "l"
      }
    }
  ],
  {
    "currencyCode": "EUR"
  }
])

Ecommerce order

A completed order calls Piwik PRO ecommerceOrder with its products, the payment information and the currency code.

Event
{
  "name": "order complete",
  "data": {
    "id": "0rd3r1d",
    "currency": "EUR",
    "shipping": 5.22,
    "taxes": 73.76,
    "value": 555
  },
  "context": {
    "shopping": [
      "complete",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "product",
      "data": {
        "id": "ers",
        "name": "Everyday Ruck Snack",
        "color": "black",
        "size": "l",
        "price": 420
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    },
    {
      "entity": "product",
      "data": {
        "id": "cc",
        "name": "Cool Cap",
        "size": "one size",
        "price": 42
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    },
    {
      "entity": "gift",
      "data": {
        "name": "Surprise"
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "235d0d7bd7a213f2",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000000000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "name": "ecommerceOrder",
  "data": {
    "set": [
      {
        "loop": [
          "nested",
          {
            "condition": {
              "$code": "e=>\"object\"==typeof e&&null!==e&&\"entity\"in e&&\"product\"===e.entity"
            },
            "map": {
              "sku": "data.id",
              "name": "data.name",
              "price": "data.price",
              "quantity": {
                "key": "data.quantity",
                "value": 1
              },
              "variant": {
                "key": "data.color"
              },
              "customDimensions": {
                "map": {
                  "1": "data.size"
                }
              }
            }
          }
        ]
      },
      {
        "map": {
          "orderId": "data.id",
          "grandTotal": "data.value",
          "tax": "data.taxes",
          "shipping": "data.shipping"
        }
      },
      {
        "map": {
          "currencyCode": {
            "key": "data.currency",
            "value": "EUR"
          }
        }
      }
    ]
  }
}
Out
window._paq.push([
  "ecommerceOrder",
  [
    {
      "sku": "ers",
      "name": "Everyday Ruck Snack",
      "price": 420,
      "quantity": 1,
      "variant": "black",
      "customDimensions": {
        "1": "l"
      }
    },
    {
      "sku": "cc",
      "name": "Cool Cap",
      "price": 42,
      "quantity": 1,
      "customDimensions": {
        "1": "one size"
      }
    }
  ],
  {
    "orderId": "0rd3r1d",
    "grandTotal": 555,
    "tax": 73.76,
    "shipping": 5.22
  },
  {
    "currencyCode": "EUR"
  }
])

Initialization

Destination bootstrap loads the Piwik PRO tracker and configures the tracker URL and app id.

Event
{
  "loadScript": true,
  "settings": {
    "appId": "XXX-XXX-XXX-XXX-XXX",
    "url": "https://your_account_name.piwik.pro/"
  }
}
Out
window._paq.push([
  "setTrackerUrl",
  "https://your_account_name.piwik.pro/ppms.php"
]);

window._paq.push([
  "setSiteId",
  "XXX-XXX-XXX-XXX-XXX"
])

Page view

A page view calls Piwik PRO trackPageView with the page title from the event, without any mapping.

Event
{
  "name": "page view",
  "data": {
    "domain": "www.example.com",
    "title": "walkerOS documentation",
    "referrer": "https://www.walkeros.io/",
    "search": "?foo=bar",
    "hash": "#hash",
    "id": "/docs/"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "pagegroup": "docs"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "f0cc5f6fec3d8eeb",
  "trigger": "load",
  "entity": "page",
  "action": "view",
  "timestamp": 1700000000000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Out
window._paq.push([
  "trackPageView",
  "walkerOS documentation"
])

Product detail view

A product view calls Piwik PRO ecommerceProductDetailView with the product and the currency code.

Event
{
  "name": "product view",
  "data": {
    "id": "ers",
    "name": "Everyday Ruck Snack",
    "color": "black",
    "size": "l",
    "price": 420
  },
  "context": {
    "shopping": [
      "detail",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [],
  "consent": {
    "functional": true
  },
  "id": "db22fb16c70f663d",
  "trigger": "load",
  "entity": "product",
  "action": "view",
  "timestamp": 1700000000000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "name": "ecommerceProductDetailView",
  "data": {
    "set": [
      {
        "set": [
          {
            "map": {
              "sku": "data.id",
              "name": "data.name",
              "price": "data.price",
              "quantity": {
                "key": "data.quantity",
                "value": 1
              },
              "variant": {
                "key": "data.color"
              },
              "customDimensions": {
                "map": {
                  "1": "data.size"
                }
              }
            }
          }
        ]
      },
      {
        "map": {
          "currencyCode": {
            "key": "data.currency",
            "value": "EUR"
          }
        }
      }
    ]
  }
}
Out
window._paq.push([
  "ecommerceProductDetailView",
  [
    {
      "sku": "ers",
      "name": "Everyday Ruck Snack",
      "price": 420,
      "quantity": 1,
      "variant": "black",
      "customDimensions": {
        "1": "l"
      }
    }
  ],
  {
    "currencyCode": "EUR"
  }
])

An event no rule maps fails with missing-input; use a rule with ignore: true to keep an event out without a failure. A consent revocation for a destination with config.consent turns off the link tracking walkerOS enabled, and a blocked ppms.js fails later events visibly.

Loading the tracker​

With loadScript: true, the destination loads ppms.js from your account URL and registers the tracker URL and site id. url and appId are required only then. When the Piwik PRO container already runs on the page, leave loadScript unset. The destination then only pushes commands to _paq.

Methods​

rule.name is a Piwik PRO JavaScript method, and data resolves to its arguments in order. An array built with set is spread, any other value is a single argument. Trailing undefined arguments are dropped. A page view without rule.name calls trackPageView, with rule.data as its arguments, or else data.title as the title.

MethodArgumentsCustom dimensions
trackPageView[title]Set around the hit
trackEventcategory, action[, name[, value[, dimensions]]]Dimensions argument
trackGoalgoalId[, conversionValue[, dimensions]]Dimensions argument
trackSiteSearchkeyword[, category[, resultCount[, dimensions]]]Dimensions argument
trackLinklinkAddress, linkType[, dimensions]Dimensions argument
trackContentImpressioncontentName[, contentPiece[, contentTarget]]Set around the hit
trackContentInteractioncontentInteraction, contentName[, contentPiece[, contentTarget]]Set around the hit
ecommerceProductDetailViewproducts[, { currencyCode }]Set around the hit
ecommerceAddToCartproducts[, { currencyCode }]Set around the hit
ecommerceRemoveFromCartproducts[, { currencyCode }]Set around the hit
ecommerceCartUpdateproducts, grandTotal[, { currencyCode }]Set around the hit
ecommerceOrderproducts, { orderId, grandTotal, subTotal, tax, shipping, discount }[, { currencyCode }]Set around the hit
pingnoneSet around the hit
Any other _paq command by its explicit name, such as setUserIdAs your mapping passes themNone

The first 13 are the portable methods: the server destination builds the same hits from the same mapping. Any other command works only in the browser. The destination passes it through as a state command: it gets no custom dimensions and does not count as a hit for link tracking.

The ecommerce methods take the currency as an optional last argument, an object with currencyCode such as { "currencyCode": "EUR" }. Add it to the set array of your mapping, for example with { map: { currencyCode: { key: 'data.currency', value: 'EUR' } } }. The destination passes arguments through to _paq as mapped.

An event without rule.name that is not a page view fails with missing-input, unless its rule has a goal, which then is sent alone (see goals). To keep unmapped events out of the destination without a failure, ignore everything with a wildcard and map what you need:

"mapping": {
  "*": { "*": { "ignore": true } },
  "page": { "view": {} }
}

Goals​

A rule's settings.goalId adds a trackGoal call after the method hit. settings.goalValue is a mapping value for its conversion value, such as "data.value". A page view rule with goal settings still tracks its page view. With silent: true, the rule sends only the goal, with its dimensions. A silent rule without a goal sends nothing and logs a skip.

A rule without name on an event that is not a page view sends only the goal when it has a goalId:

"order": {
  "complete": {
    "settings": {
      "goalId": "6a0f4c1e-3b2d-4e8f-a7c9-1d2e3f4a5b6c",
      "goalValue": "data.value"
    }
  }
}

A conversion value comes from settings.goalValue:

An order complete fires Piwik PRO trackEvent and then trackGoal with the order total as the conversion value.

Event
{
  "name": "order complete",
  "data": {
    "id": "0rd3r1d",
    "currency": "EUR",
    "shipping": 5.22,
    "taxes": 73.76,
    "value": 555
  },
  "context": {
    "shopping": [
      "complete",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "product",
      "data": {
        "id": "ers",
        "name": "Everyday Ruck Snack",
        "color": "black",
        "size": "l",
        "price": 420
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    },
    {
      "entity": "product",
      "data": {
        "id": "cc",
        "name": "Cool Cap",
        "size": "one size",
        "price": 42
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    },
    {
      "entity": "gift",
      "data": {
        "name": "Surprise"
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "235d0d7bd7a213f2",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000000000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "name": "trackEvent",
  "data": {
    "set": [
      {
        "value": "order"
      },
      {
        "value": "complete"
      },
      "data.id"
    ]
  },
  "settings": {
    "goalId": "goal_1",
    "goalValue": "data.value"
  }
}
Out
window._paq.push([
  "trackEvent",
  "order",
  "complete",
  "0rd3r1d"
]);

window._paq.push([
  "trackGoal",
  "goal_1",
  555
])

A page view rule with only a goal keeps the page view and adds the goal:

A page view rule with a goal keeps the default trackPageView and adds a trackGoal conversion.

Event
{
  "name": "page view",
  "data": {
    "domain": "www.example.com",
    "title": "walkerOS documentation",
    "referrer": "https://www.walkeros.io/",
    "search": "?foo=bar",
    "hash": "#hash",
    "id": "/docs/"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "pagegroup": "docs"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "f0cc5f6fec3d8eeb",
  "trigger": "load",
  "entity": "page",
  "action": "view",
  "timestamp": 1700000000000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "settings": {
    "goalId": "goal_1"
  }
}
Out
window._paq.push([
  "trackPageView",
  "walkerOS documentation"
]);

window._paq.push([
  "trackGoal",
  "goal_1"
])

trackSiteSearch, trackLink and ping are mapped like any other method: rule.name selects the method and data supplies its arguments.

A search submit fires Piwik PRO trackSiteSearch with the keyword, category, and result count.

Event
{
  "name": "search submit",
  "data": {
    "query": "rucksack",
    "category": "bags",
    "results": 12
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "bb0b540ecfea1d87",
  "trigger": "test",
  "entity": "search",
  "action": "submit",
  "timestamp": 1700000309,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "name": "trackSiteSearch",
  "data": {
    "set": [
      "data.query",
      "data.category",
      "data.results"
    ]
  }
}
Out
window._paq.push([
  "trackSiteSearch",
  "rucksack",
  "bags",
  12
])

A file download fires Piwik PRO trackLink with the file URL and the download link type.

Event
{
  "name": "file download",
  "data": {
    "url": "https://www.example.com/whitepaper.pdf"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "d15b71b397e23379",
  "trigger": "test",
  "entity": "file",
  "action": "download",
  "timestamp": 1700000310,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "name": "trackLink",
  "data": {
    "set": [
      "data.url",
      {
        "value": "download"
      }
    ]
  }
}
Out
window._paq.push([
  "trackLink",
  "https://www.example.com/whitepaper.pdf",
  "download"
])

A pulse-triggered event fires Piwik PRO ping to extend the time spent on the page.

Event
{
  "name": "page ping",
  "data": {
    "string": "foo",
    "number": 1,
    "boolean": true,
    "array": [
      0,
      "text",
      false
    ]
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "us3r",
    "device": "c00k13",
    "session": "s3ss10n"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "1240115bff8ccb8e",
  "trigger": "pulse",
  "entity": "page",
  "action": "ping",
  "timestamp": 1700000311,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "name": "ping"
}
Out
window._paq.push([
  "ping"
])

Custom dimensions​

Custom dimensions are keyed by bare dimension id, like { "1": "data.size" }. Each value is a mapping value, resolved for every event.

  • settings.customDimensions applies to every hit.
  • A rule's settings.customDimensions applies to that rule's hits and wins per key. A rule dimension that resolves to nothing removes that dimension from the hit, even when the destination sets it.
  • A dimensions object your mapping passes as the method's own argument wins over both settings. Its values are passed unencoded, so percent-encode them yourself. Keys other than dimension{id} are dropped.

How the dimensions reach Piwik PRO depends on the method:

  • trackEvent, trackGoal, trackSiteSearch and trackLink get them as their dimensions argument, { "dimension1": "l" }, with the values from the settings percent-encoded. The goal gets the destination and rule dimensions.
  • Page view, content, ecommerce and ping hits have no dimensions argument. Their dimensions are set just before the hit with setCustomDimensionValue and restored after it: rule-only dimensions are deleted with deleteCustomDimension, and the destination values are set again. A rule value therefore never leaks into the tracker's automatic hits, such as outlinks, while destination values stay set on purpose. This requires the Piwik PRO tracker 15.3 or later.

Identified and anonymous​

The identified setting decides whether the tracker identifies the visitor.

identifiedTracker
absent or trueIdentified, the Piwik PRO default.
falseAnonymous and without tracker cookies from init.
a consent object, like { "marketing": true }Identified while any listed consent state is granted, anonymous otherwise.

While anonymous, the tracker runs without cookies. Switching to anonymous pushes disableCookies, deleteCookies and setUserIsAnonymous(true), so a returning visitor who revoked consent loses the tracker's existing cookies too.

With a consent object, the destination starts the tracker anonymous at init, unless the collector's consent already grants identification. Before each hit it matches the tracker to the current consent: enableCookies and deanonymizeUser when identification becomes granted, which turns the cookies back on and merges the anonymous session into the identified visitor, and the anonymous commands above when it is revoked.

Use config.consent for the states that gate tracking at all, and identified for the states that gate identification:

"config": {
  "consent": { "analytics": true },
  "settings": {
    "identified": { "marketing": true }
  }
}
caution

Do not repeat the identified states in config.consent. A destination needs only one of its config.consent states granted, so { "analytics": true, "marketing": true } sends events to Piwik PRO when only marketing is granted. With { "marketing": true } alone, events wait in the queue until marketing is granted, and the tracker never sends an anonymous hit.

Automatic outlink and download tracking is on by default. The destination pushes enableLinkTracking once per page, right after the first hit it sends, such as a page view, an event or a goal. It never pushes it at init, so a page without a hit gets no automatic link tracking. Commands that set state, such as setUserId, are not hits and do not start it. Set linkTracking: false to switch it off.

Several Piwik PRO destinations​

The tracker state the destination manages, link tracking and the identified state, belongs to the collector. Two Piwik PRO destinations on one page share it: link tracking starts once, after the first hit of either destination. Give both the same identified setting, or each will switch the shared tracker back and forth.

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)