What Collectors Do

A collector is a named aggregator. As the payrun engine executes wage types in numeric order, each wage type’s result is fed into the collectors it belongs to. By the time the last wage type has executed, every collector holds a meaningful payroll aggregate — gross income, total employee deductions, net pay, or employer cost.

In the German regulation, the core collectors look like this:

Collector Purpose Typical Feeders
DE.Gesamtbrutto Gross income — base for income tax and social security WT 10 (Salary), WT 1100–1500 (allowances, bonuses)
DE.ArbeitnehmerAbzuege Employee deductions — tax + employee SS contributions WT 5100 (LSt), WT 6100–6400 (KV/PV/RV/AV AN)
DE.Nettolohn Net pay — what the employee receives Computed as Gesamtbrutto minus ArbeitnehmerAbzuege
DE.ArbeitgeberBelastung Total employer cost — gross plus employer contributions WT 6500–6800 (KV/PV/RV/AV AG), WT 6900 (UV), Umlagen

Every collector has a collectMode that controls how values are aggregated. The engine supports six aggregation modes:

CollectMode Aggregation Example
Summary Sum of all contributing values Gross income, total deductions
Minimum Smallest contributing value Lowest hourly rate across assignments
Maximum Largest contributing value Highest daily rate in period
Average Average of contributing values Mean shift differential
Range Maximum minus minimum Pay variance across components
Count Number of contributing values Number of active wage types

Summary is the default — if no collectMode is specified, the collector sums all contributing values. But the availability of Minimum, Maximum, Average, Range, and Count makes collectors a general-purpose aggregation mechanism, not just a running total.

{
  "name": "Gesamtbrutto",
  "collectMode": "Summary",
  "collectorGroups": ["GrossComponents"]
}

Collectors are defined in the regulation JSON alongside wage types and cases. They are first-class regulation objects with their own namespace rules: a collector named Gesamtbrutto in a regulation with namespace DE becomes DE.Gesamtbrutto in the system. Provider regulations reference it by that fully qualified name.

Direct Collector Membership

The simplest way to connect a wage type to a collector is direct membership. The wage type declares a collectors array listing the collectors that should receive its result.

Consider a provider wage type 9300 — an employer-specific meal voucher benefit. The voucher amount is part of gross income and part of the employer’s total cost. The wage type definition declares both collectors:

{
  "wageTypeNumber": 9300,
  "name": "MealVoucher",
  "description": "Daily meal voucher benefit",
  "valueActions": [
    "^^MealVoucherDailyRate * ^^WorkingDaysMonth"
  ],
  "collectors": [
    "DE.Gesamtbrutto",
    "DE.ArbeitgeberBelastung"
  ]
}

When WT 9300 executes and produces a result of, say, 176.00, that value is added to both DE.Gesamtbrutto and DE.ArbeitgeberBelastung. The downstream wage types that calculate income tax and social security contributions read the updated gross total and include the meal voucher in their base.

Namespace rule: Because the provider regulation has namespace ACME and the collectors belong to namespace DE, the collector references must be fully qualified: "DE.Gesamtbrutto", not just "Gesamtbrutto". Within the DE regulation itself, the short name suffices.

Direct membership is explicit and easy to audit. Looking at a wage type’s JSON, you immediately see which aggregation paths it participates in. This matters for compliance: an auditor can trace how a specific compensation component flows into gross pay, tax base, and net pay by reading the collector declarations.

The limitation appears when many wage types feed the same set of collectors. If you have twelve allowance wage types that all feed Gesamtbrutto, SvBrutto, and ArbeitgeberBelastung, maintaining identical collectors arrays across all twelve is repetitive and error-prone. That is where collector groups come in.

Collector Groups: Indirect Membership

A collector group is a string label. There is no group definition object — no registration, no configuration file. Groups exist purely by convention: if a collector declares membership in a group, and a wage type declares membership in the same group, the wage type’s result feeds that collector.

The mechanism has two sides. First, collectors declare which groups they belong to:

{
  "name": "Gesamtbrutto",
  "collectMode": "Summary",
  "collectorGroups": ["GrossComponents"]
}

{
  "name": "SvBrutto",
  "collectMode": "Summary",
  "collectorGroups": ["GrossComponents"]
}

{
  "name": "ArbeitgeberBelastung",
  "collectMode": "Summary",
  "collectorGroups": ["GrossComponents"]
}

Second, wage types declare which groups they contribute to:

{
  "wageTypeNumber": 9300,
  "name": "MealVoucher",
  "valueActions": [
    "^^MealVoucherDailyRate * ^^WorkingDaysMonth"
  ],
  "collectorGroups": ["GrossComponents"]
}

The result: WT 9300’s value is added to Gesamtbrutto, SvBrutto, and ArbeitgeberBelastung — all three collectors that belong to the GrossComponents group. Adding a fourth collector to the group later automatically includes it, without touching any wage type definitions.

Tip: A wage type can use both collectors and collectorGroups simultaneously. There is no double-apply — if a collector is reached by both paths, the value is still added only once. Use the combination when most collectors come from a group but one additional collector needs explicit inclusion.

Group names are arbitrary strings matched by exact equality. There is no hierarchy, no wildcard matching, no inheritance. The simplicity is intentional: group membership is a compile-time wiring decision, not a runtime dispatch mechanism. When you read a regulation’s JSON, you can determine every collector relationship by string-searching the group names.

The practical guidance is straightforward: use direct collectors when a wage type feeds one or two collectors. Switch to collectorGroups when three or more collectors share the same wage type membership pattern across multiple wage types. The threshold is low because the maintenance cost of keeping parallel collector arrays in sync exceeds the minor indirection cost of a group.

The .Cycle Accessor: Reading YTD Values

Collectors aggregate values within each payrun period. But many calculations need the year-to-date total — how much gross income has been paid since January, how much social security contribution has been deducted so far, whether an annual ceiling has been reached.

The .Cycle accessor provides this. In a No-Code value action, you write:

^&DE.Gesamtbrutto.Cycle

This returns the aggregated value of DE.Gesamtbrutto across all completed periods in the current payroll cycle (typically January through the previous month). The current period’s value is not included — .Cycle returns the YTD total before the current payrun adds its contribution.

The distinction matters. Consider a social security ceiling check for a German employee. The annual ceiling for pension insurance (RV) in 2026 is 96,600 EUR. The check is: has the cumulative SV gross reached the ceiling? If so, no further RV contributions are due.

// Remaining headroom before ceiling
// ^&DE.SvBrutto.Cycle = YTD SV gross through previous period
// ^&DE.SvBrutto = current period SV gross (so far in this payrun)
// Ceiling check uses the sum of both

In C# scripting, the equivalent is GetCollectorValue("DE.Gesamtbrutto", CollectorValueType.Cycle). The No-Code token ^& with .Cycle suffix is the shorthand that avoids writing a script.

Accessor Returns Use Case
^&DE.Gesamtbrutto Current period value Tax base for this month’s calculation
^&DE.Gesamtbrutto.Cycle YTD value (excluding current period) Annual ceiling checks, progressive tax lookups

Annual bonus calculations are a common use case. A wage type that pays 10% of year-to-date gross as a December bonus reads ^&DE.Gesamtbrutto.Cycle to get the January–November total, adds the current month’s gross, multiplies by 0.10, and writes the result. The collector’s cycle accessor does the heavy lifting — no manual period iteration, no SQL query, no script loop.

Clusters: Grouping Wage Types for Execution

Collectors control aggregation — which totals a wage type contributes to. Clusters control execution scope — which wage types participate in a given payrun.

Every wage type can declare a clusters array. A payrun job also declares which clusters to execute. Only wage types whose cluster membership overlaps with the payrun’s cluster list are included in the calculation. Wage types without any cluster declaration are always included.

The primary use case is the consolidation layer. Country regulations define wage types 7000–7030 that produce standardized cross-country reporting values: total gross in EUR, total employer cost, headcount. These wage types carry a cluster tag:

{
  "wageTypeNumber": 7000,
  "name": "ConsolidatedGross",
  "description": "Gross income in reporting currency (EUR)",
  "clusters": ["Consolidation"],
  "valueActions": [
    "^&DE.Gesamtbrutto"
  ]
}

A standard monthly payrun with "clusters": ["DE"] executes all regular German wage types but skips the consolidation WTs. A reporting payrun with "clusters": ["DE", "Consolidation"] executes everything — the regular calculation plus the consolidation outputs. This allows the same regulation to serve both operational and reporting purposes without duplicating wage types or maintaining separate regulation packages.

Cluster mechanics: Wage types without a clusters declaration are considered “unscoped” and always execute. Wage types with clusters only execute when at least one of their clusters matches the payrun’s cluster list. This opt-in model means adding a cluster to an existing wage type is a restriction, not an expansion.

For cross-country consolidation — aggregating payroll results from Germany, the Netherlands, and France into a single reporting view — the cluster mechanism is the entry point. Each country regulation provides WT 7000–7030 with the Consolidation cluster. The consolidation regulation then reads these standardized outputs across tenants. For the full consolidation architecture, see Cross-Country Consolidation.

Designing Collectors in Provider Overlays

When building a provider regulation that extends a country regulation, you face a recurring design question: should your wage type feed the country’s collectors, or should you create your own?

The answer depends on whether the country regulation needs to know about your value. If your wage type produces a compensation component that affects tax or social security — a bonus, an allowance, a benefit in kind — it must feed the country’s gross income collector. The tax calculation reads that collector; if your value is missing from it, the employee’s tax will be too low.

If your wage type produces a value that is purely for internal reporting — a cost center allocation, a budget reconciliation figure, a management KPI — it should feed a provider-specific collector. There is no reason for the country regulation to include it in any statutory calculation.

Scenario Approach Reason
Meal voucher (taxable benefit) Feed DE.Gesamtbrutto Must be included in income tax base
Company car (geldwerter Vorteil) Feed DE.Gesamtbrutto + DE.SvBrutto Affects both tax and social security
Internal cost allocation Create ACME.CostAllocation collector No statutory relevance
Client billing summary Create ACME.BillableTotal collector Provider reporting only

When you create your own collector in a provider regulation, namespace isolation guarantees there is no collision with the country regulation. A collector named CostAllocation in the ACME namespace becomes ACME.CostAllocation. Even if a future country regulation update adds a collector with the same short name, the two are distinct objects with separate aggregators.

A common pattern for bureaus managing multiple clients is to define a provider-level collector group. The bureau creates a ProviderGross group that includes both the relevant country collectors and a bureau-specific reporting collector. All provider wage types declare "collectorGroups": ["ProviderGross"] and automatically feed everything. When the bureau onboards a new country, they add the country’s gross collector to the group — all existing provider wage types pick it up without changes.

Tip: Country regulations document which collectors are intended for provider consumption. Check the regulation’s ProviderStubs.md for the list of collectors and their expected semantics before wiring your wage types. Using the wrong collector — for example, feeding SvBrutto with a value that is tax-relevant but not subject to social security — produces incorrect contribution calculations.

The combination of direct membership, collector groups, and namespace isolation gives provider developers a predictable composition surface. Your wage types produce values; collectors aggregate them into the totals that the country regulation consumes. The wiring is declarative, auditable, and immune to cross-namespace collisions.

Connect your wage types

See how collectors wire provider extensions into country-level tax and social security calculations.

Get in Touch →
← Previous
Lookups and Data Satellites
Next →
Stub Activation and Override