---
title: "Seeding data"
description: "Populate @anonymous() Rayfin entities with RayfinServerClient in a Node.js script — @authenticated() entities have no scripted seeding path today."
url: https://rayfin.ai/docs/data/seeding
markdown_url: https://rayfin.ai/docs/data/seeding.md
section: data
product: Rayfin
sdk_version: 1.34.0
cli_version: 1.33.2
last_updated: 2026-08-23T01:28:43-07:00
source: data/seeding.mdx
---

# Seeding data

> Populate @anonymous() Rayfin entities with RayfinServerClient in a Node.js script — @authenticated() entities have no scripted seeding path today.

Use `RayfinServerClient` from a plain Node.js script to populate development data — the
same `client.data.<Entity>` calls you use from your frontend, just run with `tsx` instead
of in a browser. A script has no browser, so it can only authenticate with the
`publishableKey` your project already generates — enough to satisfy an `@anonymous()`
grant, and nothing else. See
[No supported path for `@authenticated()` entities](#no-supported-path-for-authenticated-entities)
below if the entity you want to seed requires a signed-in user.

## What a seed script can authenticate as [#what-a-seed-script-can-authenticate-as]

`RayfinServerClient` requires `publishableKey`; `accessToken` is optional:

```typescript
new RayfinServerClient<AppSchema>({
  baseUrl: env['RAYFIN_PUBLIC_API_URL'],
  publishableKey: env['RAYFIN_PUBLIC_PUBLISHABLE_KEY'],
});
```

Use `RayfinServerClient`, not `RayfinClient` — it skips the browser-coupled `Auth` module
entirely. See
[`@microsoft/rayfin-client`](/docs/reference/sdk/rayfin-client#rayfinserverclient).

Without an `accessToken`, every request carries only the publishable key. That satisfies an
`@anonymous()` grant and nothing else — it cannot satisfy `@authenticated()`, which
requires a valid signed-in user's access token on every request (see
[Built-in roles](/docs/data/permissions#built-in-roles)).

There is no supported way for a Node.js script to acquire that token itself. Fabric SSO
signs a user in through a browser popup or the Fabric portal iframe (see
[Fabric SSO](/docs/auth/fabric-sso)) — flows a headless script cannot drive — and, per
[Sessions](/docs/auth/sessions#token-handling), Rayfin doesn't expose a way to read a token
back out for reuse elsewhere. `accessToken` on `RayfinServerClient` exists for code that
already holds a token issued by some other trusted flow — for example, a Rayfin function
forwarding the caller's own request token — not as a way to sign a script in.

Service principal credentials (`rayfin login --service-principal`) don't help here either
— they authenticate the CLI itself for deployment (`rayfin up`, `rayfin up db apply`), a
separate concern from a signed-in user's access token that `client.data.*` calls check.

## Seeding an anonymous entity [#seeding-an-anonymous-entity]

Reference or lookup data with no owner — content anyone can read, that only you create —
is the case a seed script handles cleanly, because it needs no caller identity at all:

```typescript title="rayfin/data/FaqCategory.ts"
import { entity, anonymous, uuid, text } from '@microsoft/rayfin-core';

@entity()
@anonymous('*')
export class FaqCategory {
  @uuid() id!: string;
  @text({ max: 100 }) name!: string;
}
```

```typescript title="rayfin/data/FaqEntry.ts"
import { entity, anonymous, uuid, text, one } from '@microsoft/rayfin-core';
import { FaqCategory } from './FaqCategory.js';

@entity()
@anonymous('*')
export class FaqEntry {
  @uuid() id!: string;
  @text({ max: 200 }) question!: string;
  @text({ max: 2000 }) answer!: string;
  @uuid() category_id!: string;
  @one(() => FaqCategory) category!: FaqCategory;
}
```

> [!NOTE]
> A tenant admin has to enable "Enable anonymous data access for Fabric Apps" before
> Fabric grants the `anonymous` role at all — see
> [Built-in roles](/docs/data/permissions#built-in-roles). Without it, even a correctly
> decorated `@anonymous()` entity rejects anonymous requests.

Read the backend URL and publishable key from `rayfin/.env` (generated by `rayfin up`)
rather than hardcoding them — the port and key vary per project and per deployment:

```typescript title="scripts/seed.ts"
import { readFileSync } from 'fs';
import { RayfinServerClient } from '@microsoft/rayfin-client';
import type { AppSchema } from '../rayfin/data/schema';

function loadEnv(): Record<string, string> {
  const vars: Record<string, string> = {};
  for (const line of readFileSync('rayfin/.env', 'utf-8').split('\n')) {
    const match = line.match(/^([^#=]+)=(.+)$/);
    if (match) vars[match[1].trim()] = match[2].trim();
  }
  return vars;
}

const env = loadEnv();
const client = new RayfinServerClient<AppSchema>({
  baseUrl: env['RAYFIN_PUBLIC_API_URL'],
  publishableKey: env['RAYFIN_PUBLIC_PUBLISHABLE_KEY'],
});

async function seed() {
  // Create parent records first, then children that reference their IDs.
  const category = await client.data.FaqCategory.create({ name: 'Billing' });

  await client.data.FaqEntry.create({
    question: 'How do I change my plan?',
    answer: 'Open Settings → Billing and choose a new plan.',
    category: { id: category.id },
  });
}

seed().catch(console.error);
```

Run it with `tsx`:

```bash
npx tsx scripts/seed.ts
```

Or wire it into `package.json` for a shorter command:

```json title="package.json"
{
  "scripts": {
    "seed": "tsx scripts/seed.ts"
  }
}
```

```bash
npm run seed
```

**Create parents before children.** A `@one()` relationship needs the parent's `id` to
exist first — create the `FaqCategory` before the `FaqEntry` that references it, as shown
above. See [Relationships](/docs/data/relationships) for how `@one()` / `@many()` work.

### Seeding a single entity [#seeding-a-single-entity]

For an entity with no relationships, the script is shorter — just create:

```typescript title="scripts/seed-faq-categories.ts"
import { readFileSync } from 'fs';
import { RayfinServerClient } from '@microsoft/rayfin-client';
import type { AppSchema } from '../rayfin/data/schema';

function loadEnv(): Record<string, string> {
  const vars: Record<string, string> = {};
  for (const line of readFileSync('rayfin/.env', 'utf-8').split('\n')) {
    const match = line.match(/^([^#=]+)=(.+)$/);
    if (match) vars[match[1].trim()] = match[2].trim();
  }
  return vars;
}

const env = loadEnv();
const client = new RayfinServerClient<AppSchema>({
  baseUrl: env['RAYFIN_PUBLIC_API_URL'],
  publishableKey: env['RAYFIN_PUBLIC_PUBLISHABLE_KEY'],
});

async function seed() {
  const names = ['Billing', 'Account', 'Troubleshooting'];
  for (const name of names) {
    await client.data.FaqCategory.create({ name });
  }
}

seed().catch(console.error);
```

## No supported path for `@authenticated()` entities [#no-supported-path-for-authenticated-entities]

Most application data is not anonymous — anything with a `user_id`/`owner_id` and a
row-level policy, like the `Todo` entity in [Build a todo app](/docs/recipes/todo-app), is
scoped with `@authenticated()`. As established above, a seed script cannot obtain a token
for any user, so &#x2A;*there is no supported way today to script seed data into an entity that
requires authentication.**

If you need rows in an entity like that for development or testing, create them the way a
real signed-in user would — through the running app itself, signed in with Fabric SSO —
rather than through a script. Do not hand-roll an `Authorization` header or fabricate a
token shaped like a Fabric-issued JWT: there is no supported way to mint a valid one outside
Fabric SSO, and Rayfin rejects anything that doesn't verify against its own signing keys.

```prompt title="Write a seed script for anonymous reference data"
In my Rayfin project, I have an FaqCategory entity (id, name) and an FaqEntry entity (id,
question, answer, category_id, with a @one(() => FaqCategory) relationship named
category), both decorated @anonymous('*') as public reference data with no owner. Write a
Node.js seed script at scripts/seed.ts using RayfinServerClient from
@microsoft/rayfin-client. Read RAYFIN_PUBLIC_API_URL and RAYFIN_PUBLIC_PUBLISHABLE_KEY from
rayfin/.env — do not pass an accessToken, since this client only needs to satisfy the
anonymous role. Create one FaqCategory named "Billing", then one FaqEntry in that category.
Add a "seed" script to package.json that runs it with tsx.
```
