Plum Rules Engine: validate data without code deploymentsLearn more

Journey trees · Pipeline observability · Rules engine

Pluminstruments your apps and data pipelines, then renders every run as a live logical tree. See what happened, which branch fired, and whether ingested data passed your rules.

  • Journey trees

    Customer sessions and touchpoints as inspectable graphs your whole team can read.

  • Pipeline observability

    Automations, conditionals, and loops traced step by step with full execution history.

  • Rules engine

    Centralized JSON validation per tenant. Change rules without redeploying pipelines.

Instrument once with plum.track(). Developers debug buried logic, data engineers ship safer pipelines, and leaders read platform success without waiting on engineering reports.

plum-platform
Track

Customer web journey

Live session tree

Data pipeline journey

Parallel automation flow

Build and track

Lost in legacy code? Stop zooming. Start seeing journeys

You can stare at repos all day and still not know what actually ran. Plum turns buried logic into inspectable journey trees, so the golden days of shipping return.

  • No more endless code spelunking to answer “what happened?”
  • Graphical logical trees developers and CXOs inspect together
  • Complete audit trail from first touch to outcome
legacy-monolith
Track

Before Plum

Hours lost scrolling repos

async function handleCheckout(ctx) {

const cart = await getCart(ctx.sessionId);

if (!cart?.items?.length) return;

const promo = legacyPromoLookup(cart);

// TODO: refactor after 2019 migration

if (promo && promo.stackable === 'maybe') {

applyDiscount(cart, promo, { force: true });

}

const tax = computeTax(cart, ctx.region);

await chargeGateway(cart.total + tax);

emit('order.placed', { id: cart.id });

}

 

function legacyPromoLookup(cart) {

const codes = readFileSync('./promos.json');

return JSON.parse(codes).find(p => p.active);

}

 

export const reconcileSession = (events) => {

let node = events[0];

while (node?.next) node = node.next;

return node?.payload ?? null;

};

 

if (featureFlags.OLD_PATH_ENABLED) {

routeThroughLegacyMiddleware(req, res);

}

async function handleCheckout(ctx) {

const cart = await getCart(ctx.sessionId);

if (!cart?.items?.length) return;

const promo = legacyPromoLookup(cart);

// TODO: refactor after 2019 migration

if (promo && promo.stackable === 'maybe') {

applyDiscount(cart, promo, { force: true });

}

const tax = computeTax(cart, ctx.region);

await chargeGateway(cart.total + tax);

emit('order.placed', { id: cart.id });

}

 

function legacyPromoLookup(cart) {

const codes = readFileSync('./promos.json');

return JSON.parse(codes).find(p => p.active);

}

 

export const reconcileSession = (events) => {

let node = events[0];

while (node?.next) node = node.next;

return node?.payload ?? null;

};

 

if (featureFlags.OLD_PATH_ENABLED) {

routeThroughLegacyMiddleware(req, res);

}

}

routeThroughLegacyMiddleware(req, res);

if (featureFlags.OLD_PATH_ENABLED) {

 

};

return node?.payload ?? null;

while (node?.next) node = node.next;

let node = events[0];

export const reconcileSession = (events) => {

 

}

return JSON.parse(codes).find(p => p.active);

const codes = readFileSync('./promos.json');

function legacyPromoLookup(cart) {

 

}

emit('order.placed', { id: cart.id });

await chargeGateway(cart.total + tax);

const tax = computeTax(cart, ctx.region);

}

applyDiscount(cart, promo, { force: true });

if (promo && promo.stackable === 'maybe') {

// TODO: refactor after 2019 migration

const promo = legacyPromoLookup(cart);

if (!cart?.items?.length) return;

const cart = await getCart(ctx.sessionId);

async function handleCheckout(ctx) {

}

routeThroughLegacyMiddleware(req, res);

if (featureFlags.OLD_PATH_ENABLED) {

 

};

return node?.payload ?? null;

while (node?.next) node = node.next;

let node = events[0];

export const reconcileSession = (events) => {

 

}

return JSON.parse(codes).find(p => p.active);

const codes = readFileSync('./promos.json');

function legacyPromoLookup(cart) {

 

}

emit('order.placed', { id: cart.id });

await chargeGateway(cart.total + tax);

const tax = computeTax(cart, ctx.region);

}

applyDiscount(cart, promo, { force: true });

if (promo && promo.stackable === 'maybe') {

// TODO: refactor after 2019 migration

const promo = legacyPromoLookup(cart);

if (!cart?.items?.length) return;

const cart = await getCart(ctx.sessionId);

async function handleCheckout(ctx) {

// TODO: refactor after 2019 migration

if (promo && promo.stackable === 'maybe') {

applyDiscount(cart, promo, { force: true });

}

const tax = computeTax(cart, ctx.region);

await chargeGateway(cart.total + tax);

emit('order.placed', { id: cart.id });

}

 

function legacyPromoLookup(cart) {

const codes = readFileSync('./promos.json');

return JSON.parse(codes).find(p => p.active);

}

 

export const reconcileSession = (events) => {

let node = events[0];

while (node?.next) node = node.next;

return node?.payload ?? null;

};

 

if (featureFlags.OLD_PATH_ENABLED) {

routeThroughLegacyMiddleware(req, res);

}

async function handleCheckout(ctx) {

const cart = await getCart(ctx.sessionId);

if (!cart?.items?.length) return;

const promo = legacyPromoLookup(cart);

// TODO: refactor after 2019 migration

if (promo && promo.stackable === 'maybe') {

applyDiscount(cart, promo, { force: true });

}

const tax = computeTax(cart, ctx.region);

await chargeGateway(cart.total + tax);

emit('order.placed', { id: cart.id });

}

 

function legacyPromoLookup(cart) {

const codes = readFileSync('./promos.json');

return JSON.parse(codes).find(p => p.active);

}

 

export const reconcileSession = (events) => {

let node = events[0];

while (node?.next) node = node.next;

return node?.payload ?? null;

};

 

if (featureFlags.OLD_PATH_ENABLED) {

routeThroughLegacyMiddleware(req, res);

}

async function handleCheckout(ctx) {

const cart = await getCart(ctx.sessionId);

if (!cart?.items?.length) return;

const promo = legacyPromoLookup(cart);

Still can't see what ran

Zooming through legacy code won't reveal the journey

Lost in legacy code

Hand it to Plum. Let's take the highway

plum-platform
Track

Attach Plum in code

await plum.track("web:landing", { sessionId });

await plum.track("page:/pricing", { plan: "pro" });

await plum.track("cta:signup", { source: "pricing" });

await plum.rule("email-format", payload.email);

await plum.track("flow:onboarding", { branch: "pass" });

await plum.track("user:profile", { status: "created" });

Journey tree output

1 steps materialized from your code

Session started

Inbound web event

web:landing
Signal received

Live tree

1 of 6 nodes

+44 more from this session

Alternative to

SegmentDatadogAmplitude
Scale observability

Track any flow without blind spots

Monitor data pipelines, automations, and conditional loops, logging a complete tree of how the whole process took place.

  • Data engineering pipeline tracking
  • Automation and workflow session monitoring
  • Conditional loop trees with full execution history
Plum preview
plum-platform
Track

Data pipeline journey

Live automation flow

Ingest batch

S3 → staging

pipe:ingest
Online

Parse records

Column inference

pipe:parse
Online

Validate schema

JSON rules engine

rule:schema-v2
Online

Null rate gate?

if null_rate < 0.01

rule:quality-gate
Condition truepass

Load warehouse

Pass branch proceeds

pipe:load
Online

Publish metrics

Downstream consumers

pipe:publish
Online
+10 more steps

Alternative to

AirflowPrefectDagster
Centralize validation

Validate data at the core. Change rules without redeploys

Democratize data ingestion with a centralized rules engine. Platform success reports for CXOs, validation without manual report building.

  • JSON structure validation rules per tenant
  • No code deployment. Change rules without touching pipelines
  • Platform success reports for CXOs and project owners

Rules

JSON payload

{  "email": "user@example.com",  "amount": 1250.00,  "created_at": "2026-06-20T09:41:02Z",  "tenant_id": "acme-corp"}
...emailValid email format
...amountPositive number
...created_atISO 8601 date
...tenant_idNon-empty string

Avg pass rate

99.0%

Pipelines

3

Records today

1.37M

PipelinePass rateRecordsDate
Daily ingest99.2%1,240,0002026-06-20
Partner feed A97.8%89,0002026-06-20
Partner feed B100.0%45,0002026-06-19

Alternative to

Great Expectationsdbt testsSoda

Who uses Plum

Built for every role on your data platform

Developers

Inspect logical trees and session timelines in one place. Debug conditional loops and pipeline branches 10× faster.

plum-platform
Track

Attach Plum in code

await plum.track("web:landing", { sessionId });

await plum.track("page:/pricing", { plan: "pro" });

await plum.track("cta:signup", { source: "pricing" });

await plum.rule("email-format", payload.email);

await plum.track("flow:onboarding", { branch: "pass" });

await plum.track("user:profile", { status: "created" });

Journey tree output

1 steps materialized from your code

Session started

Inbound web event

web:landing
Signal received

Live tree

1 of 6 nodes

+44 more from this session

Data engineers

Track pipelines and validate ingested data without redeploying rules. Every branch logged with full execution history.

Rules

JSON payload

{  "email": "user@example.com",  "amount": 1250.00,  "created_at": "2026-06-20T09:41:02Z",  "tenant_id": "acme-corp"}
...emailValid email format
...amountPositive number
...created_atISO 8601 date
...tenant_idNon-empty string

CXOs & product owners

Platform success reports and journey visibility without asking engineering. See exactly what happened in every customer flow.

Avg pass rate

99.0%

Pipelines

3

Records today

1.37M

PipelinePass rateRecordsDate
Daily ingest99.2%1,240,0002026-06-20
Partner feed A97.8%89,0002026-06-20
Partner feed B100.0%45,0002026-06-19

Trusted by large teams around the world

Enterprise teams use Plum to see customer journeys, pipeline runs, and validation outcomes on one platform instead of stitching together three different tools.

Read customer stories

Plum cut our time validating ingested data by 70%. Rules live in one place, and our pipelines just work.

Northwind Analytics

SC

Sarah Chen

Head of Data Engineering

For the first time our CXO can see exactly what happened in a customer journey without asking engineering for a report.

Contoso Digital

MW

Marcus Webb

VP Platform

The journey tree showed us a conditional loop bug we'd been chasing for weeks. Fixed in hours.

Fabrikam Systems

PS

Priya Sharma

Staff Engineer

...and loved by developers

Join developers building with Plum
AR

Alex Rivera

@dataflow_dev

Finally a platform that shows me the tree of how my pipeline actually ran, not just logs in a wall of text.

JL

Jordan Lee

@pipeline_queen

Changed a JSON validation rule in Plum without redeploying anything. Data engineers rejoice.

SO

Sam Okonkwo

@observability_guy

Plum bridges the gap between what code does and what ops sees. The logical tree view is a game changer.

TK

Taylor Kim

@cxo_insights

Our platform success reports now write themselves. Plum's validation engine + journey tracking = clarity.

RC

Riley Chen

@journey_hacker

Two parallel flows building in the hero demo sold me instantly. Customer journey + pipeline in one view.

Every role on the platform. One long-awaited answer.

Your whole team finally got what they waited for

Developers, data engineers, and leaders laughing again because journeys, pipelines, and validation finally live in one place.

10×

faster time to market

10×

faster debug

70%

less validation toil