Derived & Computed Values

Derived values are pure functions computed reactively from field values, other derived values, upstream connections, and collection items.

Derived & Computed Values

Derived values turn raw field inputs into meaningful numbers, strings, dates, or structures. They update reactively whenever their dependencies change and are available in results, visualizations, and the produces/consumes graph.

Basic syntax

derived: {
  myValue: {
    compute: (ctx: ComputeContext) => { /* return any value */ },
  }
}

The compute function is synchronous and pure — it should have no side effects.

The ComputeContext

interface ComputeContext {
  fields:      Record<string, any>   // unwrapped current field values
  derived:     Record<string, any>   // earlier derived values (in definition order)
  connections: Record<string, any>   // data from upstream tools (via consumes)
  collections: Record<string, any[]> // CRUD collection items
}
Order matters. Derived values are computed in the order they are defined. A derived value can reference earlier entries via derived.earlierKey, but referencing a later key returns undefined. Define dependencies before dependants.

Referencing other derived values

derived: {
  // Step 1 — contributionMargin must be defined BEFORE breakEvenUnits
  contributionMargin: {
    compute: ({ fields }) => fields.pricePerUnit - fields.costPerUnit,
  },

  // Step 2 — can safely reference derived.contributionMargin
  breakEvenUnits: {
    compute: ({ fields, derived }) =>
      derived.contributionMargin > 0
        ? Math.ceil(fields.fixedCosts / derived.contributionMargin)
        : Infinity,
  },

  // Step 3 — references two earlier derived values
  profitAtForecast: {
    compute: ({ fields, derived }) =>
      derived.contributionMargin * fields.expectedUnits - fields.fixedCosts,
  },
}

Referencing upstream connections

When a tool declares consumes, the resolved values are in ctx.connections:

// schema declares:
consumes: { pricingArpu: 'pricing.arpu' }

// derived can use:
derived: {
  ltv: {
    compute: ({ fields, connections }) =>
      connections.pricingArpu
        ? connections.pricingArpu / (fields.churnRate / 100)
        : null,
  },
}

If the upstream tool has not yet produced data (or is not connected), connections.pricingArpu is undefined/null. Always guard against this.

Referencing collections

derived: {
  interviewCount: {
    compute: ({ collections }) => collections.interviews?.length ?? 0,
  },
  avgSentiment: {
    compute: ({ collections }) => {
      const interviews = collections.interviews ?? []
      if (!interviews.length) return null
      const total = interviews.reduce((sum, i) => sum + (i.sentiment ?? 0), 0)
      return total / interviews.length
    },
  },
}

The deps hint

The deps array is an optional documentation hint listing which fields/derived keys this computation reads. It has no effect on reactivityuseProtoDerived recomputes all derived values whenever any field or collection changes. Use it as a readability aid:

breakEvenUnits: {
  deps: ['fixedCosts', 'contributionMargin'],
  compute: ({ fields, derived }) =>,
}

Formatting

The format property controls how the value is displayed in results and stat cards. It is purely presentational — the raw computed value is stored and used in subsequent computations.

derived: {
  monthlyBurn: {
    compute: ({ fields }) => fields.rent + fields.salaries + fields.software,
    format: { type: 'money', currency: '', decimals: 0 },
  },
  runwayMonths: {
    compute: ({ fields, derived }) =>
      derived.monthlyBurn > 0
        ? Math.floor(fields.totalFunds / derived.monthlyBurn)
        : Infinity,
    format: { type: 'number', decimals: 0, suffix: ' months' },
  },
  runwayDate: {
    compute: ({ derived }) => {
      if (!isFinite(derived.runwayMonths)) return null
      const d = new Date()
      d.setMonth(d.getMonth() + derived.runwayMonths)
      return d.toISOString().split('T')[0]
    },
    format: { type: 'date' },
  },
}

Available format types

typeInputExample output
'money'number€ 12,345
'percent'number (0–100)23.5 %
'number'number1,234
'date'ISO stringMar 2026
'text'stringas-is

Format options: currency, decimals, prefix, suffix.

Using derived values in results

Both results.badge and results.stats receive the full ComputeContext, so derived values are directly accessible:

results: {
  badge: ({ derived }) =>
    derived.ltvCacRatio >= 3
      ? { label: 'Healthy LTV:CAC', color: 'success' }
      : { label: 'LTV:CAC Too Low', color: 'error' },

  stats: ({ derived }) => [
    { label: 'LTV',         value: derived.ltv,         format: 'money' },
    { label: 'CAC',         value: derived.cac,          format: 'money' },
    { label: 'LTV:CAC',     value: derived.ltvCacRatio,  format: 'number' },
  ],
}

Using derived values in produces

Reference derived values to export them to downstream tools:

produces: {
  arpu:          'derived.arpu',
  mrrProjection: 'derived.mrrProjection',
}

The string format is 'derived.keyName' — the 'derived.' prefix is required for the resolver to distinguish it from raw field values.

Need a Landing Page?

Modern landing pages with optional modules (blog, docs, forms, i18n). Let's discuss your project.

Build Your MVP

Full-stack SaaS development. Expert in database design, multi-tenancy, and scalable architecture.

Deployment Help

Dockerize your backend, set up CI/CD pipelines, deploy to Cloudflare or Hetzner. Early-stage setup.

Suggest a SaaS Tool

Missing a calculator or tool? Suggest what you'd like to see on our site.