# Running locally with sample data

`backend: { provider: "mock" }` lets the app run with no env — but "mock mode" doesn't mean "populated mode". Mock providers **start out empty**: you open the calendar and there are no bookings, you open financials and there are no entries. The "sample data" promise only pays off once you **seed** the provider at construction time. This page is the direct map of the seed seams, plus two details that bite anyone consuming the published packages: the dashboard in mock mode, and `FAYZ_SDK_SOURCE`.

{% callout type="info" %}
This is about the second consumption path — code that imports the published `@fayz-ai/*` packages and assembles the app in `src/` (via `defineSaas` / plugin factories), not the manifest-first app the platform renders. If you're in the [Quickstart](/en/docs/quickstart) flow, populated mock data is the platform's responsibility; here you do it by hand.
{% /callout %}

## Why mock starts out empty

A mock provider is an in-memory implementation of a plugin's data contract. It exists so the app can **compile and run without a backend** — not to guess what kind of store you're building. Without a seed, the list is empty by design. Seeding means passing your domain objects in when you construct the provider; from then on the mock treats them as the existing "database" (creating, editing, and filtering on top of them).

There are four seams, from the most specific (a single plugin) to the most generic (a CRUD entity).

## Agenda: `createMockAgendaProvider({ seed })`

`createMockAgendaProvider` accepts a `MockAgendaSeed` (from `@fayz-ai/plugin-agenda`). You pass the seeded provider into the plugin factory via `dataProvider`:

```ts
import { createAgendaPlugin, createMockAgendaProvider } from '@fayz-ai/plugin-agenda'
import type { MockAgendaSeed } from '@fayz-ai/plugin-agenda'

const seed: MockAgendaSeed = {
  professionals: [
    { id: 'p1', name: 'Ana', /* … */ },
  ],
  bookings: [
    { id: 'b1', professionalId: 'p1', startsAt: '2026-07-20T14:00:00Z', /* … */ },
  ],
  // schedules? also accepted; any collection you omit falls back to the built-in example
}

export const agenda = createAgendaPlugin({
  dataProvider: createMockAgendaProvider({ seed }),
})
```

{% callout type="tip" %}
**Shortcut:** `createMockAgendaProvider()` **with no seed** already ships a ready-made example — a salon-style calendar with built-in professionals and time slots. It's the fastest way to see the calendar populated; use `seed` only when you want data from your own scenario (a clinic, a studio). Any collection you **omit** from the seed falls back to that built-in example.
{% /callout %}

## Financial: `createMockFinancialProvider({ seed })`

Same shape for financials. `MockFinancialSeed` (from `@fayz-ai/plugin-financial`) pre-populates the ledger — invoices, movements, accounts, payment methods:

```ts
import { createFinancialPlugin, createMockFinancialProvider } from '@fayz-ai/plugin-financial'
import type { MockFinancialSeed } from '@fayz-ai/plugin-financial'

const seed: MockFinancialSeed = {
  invoices: [ /* … */ ],
  movements: [ /* … */ ],
  // bankAccounts? falls back to two default accounts when absent; everything else stays empty if omitted
}

export const financial = createFinancialPlugin({
  dataProvider: createMockFinancialProvider({ seed }),
})
```

Unlike the calendar, financials do **not** invent invoices or movements when the seed is omitted — the collections stay empty (except `bankAccounts`, which falls back to two default accounts). If you want the module populated, pass a seed.

## Any entity: `createMockProvider(entityDef, initialData)`

For generic CRUD — your own plugin, your own entity — `@fayz-ai/core` exposes `createMockProvider`. The second argument is the initial array:

```ts
import { createMockProvider } from '@fayz-ai/core'

const provider = createMockProvider(clientEntityDef, [
  { id: 'c1', name: 'Example Customer', email: 'example@test.com' },
  { id: 'c2', name: 'Another Customer', email: 'another@test.com' },
])
```

The first argument can be the `EntityDef` (which is where it derives the searchable fields and the default sort from) or just an array of searchable keys. `initialData` defaults to `[]` — hence the emptiness.

## A SaaS CRUD page: `createCrudPage(entity, { mockData })`

At the page level, `createCrudPage` (from `@fayz-ai/saas`) accepts `mockData` directly in its options — handy when you're assembling a list screen without an explicit provider:

```ts
import { createCrudPage } from '@fayz-ai/saas'

const ClientsPage = createCrudPage(clientEntity, {
  mockData: [
    { id: 'c1', name: 'Example Customer' },
    { id: 'c2', name: 'Another Customer' },
  ],
})
```

## The dashboard shows zeros in mock mode

`plugin-dashboard` reads Supabase **directly** to compose its KPIs — it doesn't go through the other plugins' mock providers. In mock mode, with no Supabase configured, the cards come back **zeroed out**. Seeding the calendar or the financials doesn't change that: the dashboard can't see those seeds.

The honest way out is to **not** rely on the built-in dashboard in mock mode, and instead register **custom widgets with `compute`** that derive from the same seeds. A KPI-type widget accepts a `compute?: () => Promise<KpiValue>` — you calculate the number from your own sample data:

```ts
// a widget that derives the number from the seeds instead of reading Supabase
{
  id: 'custom:open-invoices',
  compute: async () => ({ value: seed.invoices.filter((i) => i.status === 'open').length }),
}
```

That way the dashboard reflects the mock scenario instead of showing zeros. Once you switch to the real Supabase backend, the built-in dashboard starts computing for real and those custom widgets become optional.

## `FAYZ_SDK_SOURCE`: pin the published packages

The `fayzVite` helper (from `@fayz-ai/sdk/vite`) has a convenience behavior for the monorepo: if a `fayz-sdk` checkout exists **next to** the app (at `../../fayz-sdk`), it aliases the `@fayz-ai/*` imports to that monorepo's **local source**, not to `node_modules`. Great if you're developing the SDK; a trap for a standalone app that just wants to consume the published packages — suddenly the app is running against unpublished source that happens to be on disk.

The control for this is the `FAYZ_SDK_SOURCE` env var. A standalone app should pin it to `published` in its scripts, guaranteeing it always resolves packages from `node_modules`:

```json
{
  "scripts": {
    "dev": "FAYZ_SDK_SOURCE=published vite",
    "build": "FAYZ_SDK_SOURCE=published vite build"
  }
}
```

With `FAYZ_SDK_SOURCE=published`, `fayzVite` ignores any neighboring checkout and uses exactly the versions in your `dependencies` — which is what you want when testing the app the way an external developer would.

---

Next: the second consumption path in [Headless](/en/docs/apps/headless).
