Projects
Tasks, time tracking, billable time, approvals, invoices and meetings for project work.
Covers work that is tracked and billed: tasks, time entries priced from a rate card, approvals of the time, invoices from the approved hours and meetings.
better-supabase add projectsexport default defineConfig({ blocks: { projects: {} } });add lists projects in blocks, writes the modules of every part and wires the client in lib/blocks.ts. Import the manifest from better-supabase/blocks/projects when you build the set with createBlocks.
import { projectsBlock } from "better-supabase/blocks/projects";Parts
Installing projects installs these blocks. Each stays an ordinary block in the config and keeps its own client.
Helpers
approveAndInvoice: Approve a timesheet, then invoice its billable time. A refused approval invoices nothing.
await blocks.projects.approveAndInvoice({ timesheetId });utilization: The hours every member tracked in the ISO week that holds weekOf, against a capacity (40 hours by default). An entry that crosses the edge of the week counts only the part inside it, and a running entry is skipped. It needs time_tracking.read.
const week = await blocks.projects.utilization({
weekOf: Temporal.PlainDate.from("2026-11-04"),
capacityHours: 36,
});financials: The time tracked on a project and its billable value. Each finished entry is valued at its hourly rate and rounded half to even, and the values are summed per currency, so currencies never mix.
const { seconds, billable } = (
await blocks.projects.financials({ projectId })
).orThrow();retainerDrawdown: How much of a prepaid retainer the project's billable time has used. remaining never drops below zero and exhausted turns true once the retainer is spent. Entries in another currency than the retainer's are not counted.
await blocks.projects.retainerDrawdown({
projectId,
retainer: money(500_000n, "EUR"),
});The Next.js example calls these from the weekUtilization and projectBudget server actions in composite-actions.ts.
The helper returns a Result; each step is idempotent, so retrying after a partial failure is safe.
Remove
better-supabase remove projects
better-supabase remove projects --with-partsThe first command replaces projects in blocks with its parts. The second removes the parts too, except any part another block still needs (remove).
Last updated on