uidudevelopers
Domains

Goals (OKRs)

Read the workspace's objectives and key results, measure progress against time, and update a goal's values.

  • Use it for: Server integration · Custom app
  • Reads: Bearer (apiKey) — server-side only
  • Writes: Bearer (apiKey) — server-side only
  • In a custom app: with the signed-in member's session, no token to configure — see Custom app
  • CLI: uidu goals, uidu timeframes
  • Auth: what each token can do and where it may run — Authentication

Goals are native to every uidu workspace: an organisation's OKRs already live there. Read them to build dashboards ("objectives behind schedule"), weekly check-in screens or reports — don't copy them into a Model.

Mental model

Timeframe   (name, startDate, endDate, year, active)     "Q4 2026"
  └─ Goal   objective — parentId: null
       ├─ metricKind, initialValue, currentValue, targetValue, progress, status, currency
       └─→ subgoals    key results — Goals whose parentId is the objective
  • Objectives and key results are the same type. A key result is a Goal whose parentId is its objective. listGoals returns both, flat and newest first — filter !g.parentId for the objectives and group the rest by parentId. Without first it returns all of them. getGoal returns one goal with its key results as subgoals (a Relay connection: subgoals.edges[].node); subgoalsCount is on every goal.
  • metricKind is number, percentage, currency (in goal.currency), checkbox or subgoal (measured by its key results).
  • status is the owner's own judgement: on_track, needs_attention, off_track, accomplished. It is not computed from the numbers — show it next to isGoalBehind, it says something different.
  • Timeframes. listTimeframes returns id, name, startDate, endDate, year and active (the current ones). endDate is the last day of the period, and the time helpers count it as a whole day.
  • Auth. Reads work with any client that may see the workspace, including a custom app's session token — which sees the workspace-level goals and those of the Space the app sits in. The SDK asks only for the fields a session may read; a goal's owner, members and activity aren't part of what it reads (asking for them fails the whole query for a session). updateGoal needs an account Bearer token (apiKey), server-side: a custom app cannot write goals.

Values are stored x100

initialValue, currentValue and targetValue never hold the number a person typed: uidu keeps them multiplied by 100 (a 45% target is 4500, €1,200 is 120000). progress is percent x100 too — 10000 is done. Don't divide by hand: use the helpers below to read, and toGoalRaw to write.

The helpers are pure functions over a goal:

HelperReturns
goalValue(goal, raw)A stored value in the unit a person typed: percent points for percentage (45 = 45%), whole units of goal.currency for currency, 1 or 0 for checkbox. null stays null.
toGoalRaw(value)The inverse, for writes: toGoalRaw(45) → 4500, rounded to an integer.
goalProgress(goal)How far the goal is, 0 to 1 — above 1 when it overshoots. Reads goal.progress.
goalTimeElapsed(goal, now?)How much of the timeframe has passed, 0 to 1, or null when the goal has no timeframe.
isGoalBehind(goal, { tolerance, now })true when less is done than time has elapsed, by more than tolerance (default 0; 0.1 = ten points), measured at now (default new Date()). false for a goal with no timeframe or one that is accomplished.

Use cases

OKR dashboard of goals behind schedule

Goal: a server-rendered page listing this quarter's objectives, their key results, and which ones are behind — next to what their owners say.

// app/okr/data.ts — server-only
import {
  listGoals,
  listTimeframes,
  goalValue,
  goalProgress,
  goalTimeElapsed,
  isGoalBehind,
} from '@uidu/client';
import { uiduAdmin } from '@/lib/uidu-admin'; // createClient({ workspace, apiKey })

export async function okrDashboard() {
  const [goals, timeframes] = await Promise.all([
    listGoals(uiduAdmin), // every goal, objectives and key results alike
    listTimeframes(uiduAdmin),
  ]);
  const current = new Set(timeframes.filter((t) => t.active).map((t) => t.id));

  const objectives = goals.filter((g) => !g.parentId && g.timeframe && current.has(g.timeframe.id));
  const keyResultsOf = (id: string) => goals.filter((g) => g.parentId === id);

  return objectives.map((g) => ({
    name: g.name,
    timeframe: g.timeframe?.name, // 'Q4 2026'
    done: goalProgress(g), // 0..1
    elapsed: goalTimeElapsed(g), // 0..1, null without a timeframe
    behind: isGoalBehind(g, { tolerance: 0.1 }),
    status: g.status, // the owner's view — show it alongside, don't replace it
    keyResults: keyResultsOf(g.id).map((kr) => ({
      name: kr.name,
      current: goalValue(kr, kr.currentValue), // 32 for 32%, 1200 for €1,200
      target: goalValue(kr, kr.targetValue),
      unit: kr.metricKind === 'currency' ? kr.currency : kr.metricKind,
    })),
  }));
}
  • Render done as ${Math.round(done * 100)}%; it can exceed 100% when a goal overshoots.
  • getGoal(uiduAdmin, { id }) is the cheaper call for a single objective's page: it returns the goal plus subgoals.edges[].node, each with the same fields.

Show the Space's goals in a custom app

Goal: an app installed in a Space shows that Space's objectives behind schedule, with the signed-in member's session and no token to configure.

'use client';
import { useEffect, useState } from 'react';
import { useUiduApp } from '@uidu/react';
import { listGoals, isGoalBehind, goalProgress } from '@uidu/client';

type GoalRow = Awaited<ReturnType<typeof listGoals>>[number];

export function BehindSchedule() {
  const app = useUiduApp();
  const client = app.status === 'ready' ? app.client : null; // the member's session
  const [behind, setBehind] = useState<GoalRow[]>([]);

  useEffect(() => {
    if (!client) return;
    // workspace-level goals and those of the Space the app sits in
    listGoals(client).then((goals) =>
      setBehind(goals.filter((g) => !g.parentId && isGoalBehind(g, { tolerance: 0.1 }))),
    );
  }, [client]);

  return (
    <ul>
      {behind.map((g) => (
        <li key={g.id}>
          {g.name} — {Math.round(goalProgress(g) * 100)}% ({g.status})
        </li>
      ))}
    </ul>
  );
}
  • Read-only: the session can't call updateGoal. For a check-in form, post to your own server, which writes with the Bearer (next use case). The bridge and its states: Custom app.

Weekly check-in from a server action

Goal: a form where an owner records this week's value and status; the server writes it.

'use server';
import { updateGoal, toGoalRaw } from '@uidu/client';
import { uiduAdmin } from '@/lib/uidu-admin';

export async function checkIn(goalId: string, value: number, status: string) {
  const result = await updateGoal(uiduAdmin, {
    input: {
      id: goalId,
      attributes: {
        currentValue: toGoalRaw(value), // 32 (%) → 3200; never the typed number
        status, // 'on_track' | 'needs_attention' | 'off_track' | 'accomplished'
      },
    },
  });

  if (result?.errors?.length) {
    return { ok: false, errors: result.errors.map((e) => e.name) };
  }
  return { ok: true, progress: result?.goal?.progress }; // percent x100
}
  • Values are raw integers: toGoalRaw for currentValue, targetValue and initialValue — for a checkbox goal, toGoalRaw(1) when done. The updated goal is at result.goal (no .node), with its recomputed progress.
  • From a terminal: uidu goals update <id> --attributes '{"currentValue":3200}' — the CLI takes raw values too. See CLI commands.

API at a glance

Every @uidu/client function in Goals (OKRs), generated from the source — the full reference has the rest of the SDK.

FunctionKindAuthCLIDescription
getGoalreadBearergoals getOne goal with its key results (subgoals).
goalProgresshelper——How far the goal is, 0 to 1 (above 1 when it overshoots). goal.progress itself is percent x100: 10000 is done.
goalTimeElapsedhelper——How much of the goal's timeframe has passed, 0 to 1, or null when it has none. A goal whose goalProgress is below this is behind schedule.
goalValuehelper——A stored goal value (initialValue, currentValue, targetValue) in the unit a person typed: uidu keeps every one of them x100. For percentage that is percent points (45 = 45%), for currency whole units of goal.currency, for checkbox 1 or 0. null stays null.
isGoalBehindhelper——Behind schedule: less done than time elapsed, by more than tolerance (0.1 = ten points). false for a goal with no timeframe or one already accomplished. The goal's own status (off_track, needs_attention) is its owner's judgement — show it next to this, don't replace one with the other.
listGoalsreadBearergoals listEvery goal of the workspace (objectives and key results alike), newest first. Without first, all of them.
listTimeframesreadBearertimeframes listThe workspace's timeframes (the periods goals run over, active marks the current ones) as a flat array; page with first/after.
toGoalRawhelper——The inverse of goalValue, for updateGoal.
updateGoalwriteBearergoals updateAuthoring (Bearer / apiKey). Returns the payload, incl. errors. initialValue / currentValue / targetValue are stored x100: pass toGoalRaw(value), never the number a person typed.

Types: Goal, Timeframe

CLI:

Entitylistgetcreateupdatedelete
goals✓✓—✓—
timeframes✓————

On this page