# Projects

> Tasks, time tracking, billable time, approvals, invoices and meetings for project work.

Source: https://bettersupabase.com/docs/blocks/projects

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.

```bash
better-supabase add projects
```

```ts title="better-supabase.config.ts"
export 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`](/docs/blocks/create-blocks).

```ts
import { projectsBlock } from "better-supabase/blocks/projects";
```

## Parts [#parts]

Installing `projects` installs these blocks. Each stays an ordinary block in the config and keeps its own client.

- [`tasks`](/docs/blocks/tasks)
- [`time-tracking`](/docs/blocks/time-tracking)
- [`billable-time`](/docs/blocks/billable-time)
- [`approvals`](/docs/blocks/approvals)
- [`invoices`](/docs/blocks/invoices)
- [`meetings`](/docs/blocks/meetings)

## Helpers [#helpers]

`approveAndInvoice`: Approve a timesheet, then invoice its billable time. A refused approval invoices nothing.

```ts
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`.

```ts
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.

```ts
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.

```ts
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 [#remove]

```bash
better-supabase remove projects
better-supabase remove projects --with-parts
```

The first command replaces `projects` in `blocks` with its parts. The second removes the parts too, except any part another block still needs ([`remove`](/docs/cli/init#remove)).