Writing a function
The rayfin/functions project layout, registering functions with udf.func in function_app.ts, typed data access, and the auto-generated types.ts schema.
This page covers the rayfin/functions/ project itself — its layout, how to register a
function, how to read and write your entities from inside one, and how its generated types
work.
Warning
Functions are experimental and are not available in every Fabric region or tenant. See Functions before you depend on them.
Project layout
After npx rayfin functions init, the functions project lives at rayfin/functions/:
rayfin/
data/ ← entity classes (shared via TS project references)
functions/
src/
function_app.ts ← register functions here
types.ts ← auto-generated by typegen — never hand-edit
tsconfig.json ← references: [{ "path": ".." }]
package.json
host.json
local.settings.jsonrayfin/functions/tsconfig.json uses composite: true with a project reference to
rayfin/, so functions can import type from your data entities without duplicating type
definitions.
Registering a function
Every function is registered with udf.func(name, handler, []) from
@microsoft/fabric-user-data-functions:
import { UserDataFunctions } from '@microsoft/fabric-user-data-functions';
const udf = new UserDataFunctions();
/**
* A simple greeting function.
*
* The input/output types for this function are autogenerated in ./types.ts so that
* RayfinClient can invoke it with full type-safety from your frontend app.
*/
udf.func('helloWorld', (firstName: string, lastName: string): string => {
console.log(`helloWorld invoked for ${firstName} ${lastName}`);
return `Hello ${firstName} ${lastName}!`;
}, []);- The first argument is the function name — it must match the key typegen produces in
AppFunctionsSchema. - The second argument is the handler: typed parameters plus a return type, both extracted by
typegen into
types.ts. - The third argument is reserved for future middleware — pass an empty array,
[]. - Always register functions with
udf.func(...). Don't export bare functions instead — typegen only sees registrations made throughudf.func.
Typed data access with RayfinContext
Add a RayfinContext parameter to reach the same data client client.data.<Entity> uses on
the frontend — .select().where().execute(), the same chain throughout:
import { UserDataFunctions, type RayfinContext } from '@microsoft/fabric-user-data-functions';
type AppSchema = {
Entry: { id: string; message: string; createdAt: string };
};
const udf = new UserDataFunctions();
udf.func('getEntries', async (
ctx: RayfinContext<AppSchema>
): Promise<{ id: string; message: string }[]> => {
console.log('getEntries invoked');
const data = ctx.getDataClient();
return data.Entry.select(['id', 'message', 'createdAt']).execute();
}, []);
udf.func('addEntry', async (
message: string,
ctx: RayfinContext<AppSchema>
): Promise<void> => {
console.log('addEntry invoked');
const data = ctx.getDataClient();
await data.Entry.create({ message });
}, []);Pass your AppSchema type — the same schema type you already pass to RayfinClient<AppSchema>
on the frontend — as the generic parameter, so getDataClient() only allows valid entity
names and fields. A bare RayfinContext (no generic) still works, but getDataClient()
returns untyped (Record<string, any>) access instead.
RayfinContext is a runtime-injected parameter, so typegen automatically strips it from the
generated input type — addEntry's generated input is { message: string }, not
{ message: string; ctx: RayfinContext }.
RayfinContext API:
| Member | Description |
|---|---|
ctx.getDataClient() | Returns the entity data client — typed when using RayfinContext<AppSchema>. |
ctx.baseUrl | The Rayfin endpoint URL (readonly). |
ctx.accessToken | The auth token for the current request (readonly). |
ctx.publishableKey | The Rayfin publishable key (readonly). |
ctx.getSecret(name) | Returns a secret — see below. |
ctx.getToken(audienceType) | Returns a delegated token for an external resource — see Connections. |
- Import
RayfinContextfrom@microsoft/fabric-user-data-functions— not from@microsoft/rayfin-functions. - Use
console.log(...)/console.error(...)for logging, notctx.log. - Import data entity classes with
import type, not a runtimeimport— a runtime import of@microsoft/rayfin-coredecorators would pull unnecessary dependencies into the functions bundle. Use the.jsextension on relative imports, matching ESM resolution:
import type { TodoItem } from '../../data/TodoItem.js';This works because rayfin/functions/tsconfig.json has "references": [{ "path": ".." }]
pointing at rayfin/tsconfig.json.
Function secrets
Use ctx.getSecret(name) when a function needs a secret value. The runtime checks
host-provided invocation secrets first, then falls back to process.env[name]:
udf.func('readApiKey', async (ctx: RayfinContext): Promise<string> => {
const apiKey = ctx.getSecret('THIRD_PARTY_API_KEY');
if (!apiKey) {
throw new Error('Missing THIRD_PARTY_API_KEY secret.');
}
return apiKey;
}, []);Set project secrets with rayfin secret set <NAME>.
Generated types
The CLI parses every udf.func() call under rayfin/functions/src/ and generates
types.ts:
export type AppFunctionsSchema = {
helloWorld: {
input: { firstName: string; lastName: string };
output: string;
};
getEntries: {
input: void; // RayfinContext was stripped
output: Entry[];
};
};rayfin functions initruns typegen once after scaffolding, to seedtypes.ts.- Never hand-edit
types.ts— it's regenerated from yourudf.func()calls and any manual changes are overwritten. Promise<T>return types are unwrapped toTin the generated schema.
See Calling functions from your app for importing
AppFunctionsSchema on the frontend.
In my Rayfin project, add a new function to rayfin/functions/src/function_app.ts (running
`npx rayfin functions init` first if that directory doesn't exist yet).
Register it with udf.func('<name>', handler, []) and give the handler a
RayfinContext<AppSchema> parameter so ctx.getDataClient() is fully typed against my entities
— use the same AppSchema type my frontend already passes to RayfinClient<AppSchema>. Import
RayfinContext from @microsoft/fabric-user-data-functions, not @microsoft/rayfin-functions,
and use import type for any data entity classes.
After adding it, confirm it appears in the regenerated rayfin/functions/src/types.ts — never
hand-edit that file — then deploy with `npx rayfin up` (or `npx rayfin up functions deploy`)
and tell me how to call it from the frontend.Functions
Run server-side TypeScript in Rayfin — when to use functions instead of client-side data access, and how to enable and scaffold them.
Connections
Connect a Rayfin function to external services with delegated auth — AudienceType values, the ctx.getToken() pattern, and SQL/Key Vault/OneLake examples.