Microsoft Fabric Apps: Complete TypeScript Developer and Architect Guide
Every Fabric app is four generated services — hosting, GraphQL API, auth, and SQL database — deployed from one TypeScript codebase.
Microsoft Fabric Apps is the new item type Microsoft introduced in public preview at Build 2026, and it fundamentally changes what building on Fabric means for a developer. Instead of a report, a pipeline, or a notebook, Microsoft Fabric Apps lets you define an entire application backend - schema, API, auth, hosting as TypeScript and deploy all of it with a single CLI command. This is the developer- and architect-level reference: every entity decorator, every CLI command, the full security model, and the edges you'll actually hit in preview.
Nothing here is written for an executive audience. If you're a Fabric developer, a data engineer extending a Lakehouse with a write path, a Power BI professional building an operational companion app, or a solution architect deciding whether Microsoft Fabric Apps belongs in a reference architecture, this is written for you directly with runnable code, not slideware.
Microsoft Fabric Apps entered public preview at Microsoft Build 2026 (June 2, 2026). It carries supplemental preview terms, best-effort support, and no SLA. Do not commit production-critical workloads until General Availability.
Why Microsoft Introduced Fabric Apps
Fabric unified data engineering, warehousing, and BI on top of OneLake, but the one layer that stayed outside that story was the application layer the CRUD tool, the internal dashboard with write-back, the persistent-state backend an AI agent needs. Teams building that layer routinely reached outside Fabric entirely: a Postgres instance on Supabase or Neon, a hand-rolled Express API, a separate auth provider. That works, but it creates a second, ungoverned copy of enterprise data sitting outside sensitivity labels, workspace RBAC, and capacity billing exactly the kind of shadow IT a Fabric governance program exists to prevent.
Two forces pushed Microsoft to close that gap now. First, AI coding agents have made frontend generation nearly free, a working UI in under an hour is routine while the backend behind it (auth, schema, access policy, compliance wiring) is still a multi-week effort bolted on too late, if at all. Second, whatever that application writes eventually needs to land back in the governed estate, and a separate datastore means a separate ETL pipeline maintained indefinitely. Fabric Apps, built on the open-source Rayfin SDK, is Microsoft's answer: a TypeScript-first, agent-friendly way to define a backend that writes directly into OneLake, so the data is immediately usable by every other Fabric workload without a hand-off.
"The fastest way to lose control of your data estate is to let AI-generated application backends live outside it. Fabric Apps exists to close exactly that gap."
What Microsoft Fabric Apps Are: Key Features and Capabilities
A Fabric app is a managed backend-as-a-service item that lives in a Fabric workspace. You author it locally as a TypeScript project, and a single deploy provisions four generated services inside your tenant:
@entity(), @text(), @uuid(), and related decorators. Microsoft Fabric Apps generates the SQL schema and a GraphQL API from them automatically.Two prerequisites gate all of this: the workspace needs a Fabric capacity assigned (Fabric Apps consumes capacity units from that pool, same as any other workload), and a tenant administrator must explicitly enable the Fabric Apps workload under Tenant settings before anyone in the tenant can create one optionally scoped to specific security groups rather than the whole org.
Problems Fabric Apps Solve
Four recurring pain points map directly onto Fabric Apps' design choices. Datastore proliferation, every internal tool spinning up its own database is solved by giving every app a Fabric-managed SQL database by default, no separate provisioning step. Reinvented authentication - every team writing its own login flow, token refresh, and session logic is solved by making Entra ID SSO a built-in, non-optional default in production. Governance blind spots application data nobody on the platform team can see or audit is solved by writing everything through OneLake, inheriting workspace RBAC and sensitivity labels automatically. And slow time-to-production for AI-agent backends - agents need persistent, structured state, not just stateless function calls is solved by giving an agent a governed CRUD API it can call the moment the schema is deployed.
Core Architecture
Two layers make up the system. The Rayfin CLI and SDK is the local toolchain: it scaffolds projects, runs a Docker-based local stack for development, and talks to Fabric only when you deploy. The Fabric app runtime is the cloud execution environment inside your tenant the managed service that appears in your workspace after rayfin up. Every deployed app exposes one endpoint that fronts all backend services:
https://<your-app>-app.rayfin.windows.net/
├── /api/graphql → Data API used by the generated client
├── /auth → Authentication service (Entra ID / Fabric SSO)
└── /storage → Static asset + file storage (OneLake-backed)
Each child service - SQL database, authentication, static content is provisioned as its own item under the Fabric app in the portal. That matters architecturally: each one is independently subject to workspace RBAC, sensitivity labels, and the platform's existing governance controls, rather than being an opaque black box bundled inside a single deployment artifact.
Microsoft Fabric Apps: TypeScript Development and Data Models
Every part of a Fabric app data models, backend logic, and client code is written in TypeScript. There's no separate schema language, no YAML DSL for entities, and no SQL migration files to hand-author. A data model is a plain class:
import { entity, uuid, text, int, decimal, boolean, date, set }
from '@microsoft/rayfin-core';
@entity()
export class Product {
@uuid() id!: string;
@text({ min: 1, max: 120 }) name!: string;
@decimal() price!: number;
@int({ min: 0 }) stockQuantity!: number;
@boolean({ default: true }) isAvailable!: boolean;
@date() createdAt!: Date;
@set('draft', 'published', 'archived') status!: 'draft' | 'published' | 'archived';
}
Fabric Apps reads the decorators at build time and generates the SQL DDL, the GraphQL schema, and a typed client proxy for Product three artifacts from one source of truth. Every entity gets an automatic UUID-typed id primary key unless you declare your own; composite primary keys and custom key column names aren't supported in preview.
Relationships are declared with @one() on the child side and @many() on the parent side:
@entity()
export class Category {
@uuid() id!: string;
@text() name!: string;
@many(() => Product, 'category') products!: Product[];
}
@entity()
export class Product {
@uuid() id!: string;
@text() name!: string;
@one(() => Category) category!: Category;
}
Fabric Apps generates the foreign-key column (category_id) automatically following a {property}_id convention. Many-to-many relationships aren't modeled directly, you build an explicit join entity with two @one() references. Every new entity must also be registered in rayfin/data/schema.ts so the client generator can produce typed proxies against it; forgetting this step is one of the most common early mistakes and produces a client method that silently doesn't exist.
The Decorator Reference
Decorators are the mechanism that turns a plain class into a fully generated backend. The core field decorators are @uuid(), @text() (with optional min/max length constraints), @int() and @decimal() for numeric fields, @boolean() with an optional default, @date() for timestamps, and @set(...) for enum-style constrained string values. Authorization is expressed with @role(), applied at the entity level:
@entity()
@role('authenticated', '*', {
policy: (claims, item) => claims.sub.eq(item.user_id),
})
export class Task {
@uuid() id!: string;
@text() title!: string;
@text() user_id!: string;
}
Here, @role('authenticated', '*', { policy }) means: any authenticated caller can attempt any operation (*) on Task, but the policy function narrows every read and write down to rows where the row's user_id matches the caller's subject claim. That single decorator replaces what would otherwise be a hand-written authorization middleware layer sitting in front of every route.
GraphQL APIs and the Type-Safe Client
Fabric Apps generates a full GraphQL schema from your entities, but in normal day-to-day development you don't write GraphQL queries by hand, you use the generated RayfinClient, a fluent, chainable API that builds the query underneath and gives you compile-time checking on every field, filter, and relationship you touch:
import { RayfinClient } from '@microsoft/rayfin-client';
const client = new RayfinClient<AppSchema>({
baseUrl: import.meta.env.VITE_RAYFIN_API_URL,
publishableKey: 'pk-your-project-key',
});
const active = await client.data.Product
.select(['id', 'name', 'price', 'category.name'])
.where({ status: { eq: 'published' }, stockQuantity: { gt: 0 } })
.orderBy({ price: 'desc' })
.execute();
Filters support the standard comparison set — eq, ne, gt, gte, lt, lte, contains — and dotted paths like category.name pull related-entity fields into the same request instead of triggering a second round trip. Large result sets should always go through cursor-based pagination:
const page = await client.data.Product
.select(['id', 'name', 'price'])
.orderBy({ createdAt: 'desc' })
.first(25)
.executePaginated();
// page.items, page.pageInfo.endCursor, page.pageInfo.hasNextPage
Two preview gaps worth designing around: there's no count() method on the client, and totalCount isn't populated on paginated results a UI that needs a running total currently needs its own workaround, such as a maintained counter entity or a client-side estimate.
CRUD Operations in Depth
Create, update, and delete follow the same fluent pattern as reads. A single-record create:
const created = await client.data.Product.create({
name: 'Fabric Capacity Add-on',
price: 499.0,
stockQuantity: 25,
isAvailable: true,
createdAt: new Date(),
status: 'draft',
});
Updates take a key selector and a partial payload, so you only send the fields that changed:
await client.data.Product.update({ id: created.id }, { status: 'published' });
Deletes take the same key selector:
await client.data.Product.delete({ id: created.id });
Creating a record with a relationship set on it means passing the related entity's ID, not the full object:
const item = await client.data.Product.create({
name: 'Fabric Capacity Add-on',
price: 499.0,
stockQuantity: 25,
isAvailable: true,
createdAt: new Date(),
status: 'draft',
category: { id: existingCategoryId },
});
Because there's no built-in support for multi-step transactions or stored procedures in preview, any operation that needs several writes to succeed or fail together has to be handled at the application layer with explicit compensation logic not assumed to be transactional. This is one of the more consequential architectural constraints to flag early.
The Managed SQL Database
Every Fabric app provisions its own SQL database automatically, with schema generated entirely from your TypeScript entities. You can open it directly in the Fabric portal's object explorer and run read queries against it, genuinely useful for debugging a production issue without adding logging but the portal treats it as read-only for schema purposes by design. Schema changes must originate in code and flow through the CLI's schema-apply step; editing the schema directly in the SQL database item can produce a drift between what's deployed and what your code expects, breaking the app on the next deploy.
The architectural payoff is downstream: because the database is backed by OneLake, everything a Fabric app writes is immediately visible to the rest of the Fabric estate - Lakehouses, Warehouse SQL endpoints, notebooks, and Power BI semantic models with no separate ETL job to build.
Authentication with Microsoft Entra ID
In production, Microsoft Entra ID via Fabric SSO is the only supported authentication path there's no bring-your-own-identity-provider option for a deployed Fabric app. Users authenticate through their existing Fabric identity; there's no separate signup flow or credential store to build or secure. During local development, an email-and-password fallback lets you test the full application without a live Fabric deployment, which is a genuine convenience for rapid iteration, but it's strictly a local-dev mechanism. Authenticated users are visible directly in the SQL database, which gives you a straightforward audit trail of who has actually used an internal tool without building separate logging.
Security Model, Permissions, and Authorization
Fabric Apps layers three distinct controls. Row-level authorization is declared in code via @role() policies, evaluated per-request against the caller's claims, this is the only layer that can express "users can only see their own rows" logic. Item-level permissions sit on top, at the Fabric workspace level, in three tiers: Run and interact (open and use the app), Edit (deploy code, change schema, manage the child services), and Reshare (grant access to others). Workspace roles don't automatically cascade into item permissions sharing an app with a colleague still means granting the item-level permission explicitly.
| Security responsibility | Provided by Fabric | Owned by your team |
|---|---|---|
| Sign-in / SSO | ✓ Entra ID, PKCE, HTTPS | — |
| Row-level access rules | ✓ enforced via @role decorators | ✓ you write the policy logic |
| Workspace / item permissions | ✓ RBAC tiers built in | ✓ you grant them correctly |
| Secrets in frontend code | — | ✓ static content is public by default |
| Transactional integrity across writes | — no built-in multi-step transactions | ✓ compensation logic in app code |
Hosting, Deployment, and the Application Lifecycle
Static frontend assets, HTML, CSS, JS - are hosted directly on OneLake-backed storage; there's no separate CDN or hosting account to configure. Deployment is a single CLI command, npx rayfin up, which applies schema changes, deploys the GraphQL API and auth configuration, and pushes the built frontend, all in one step. Schema-only changes can be applied independently with npx rayfin up db apply; because a destructive change can lose data, that path requires an explicit --force flag.
Multi-environment promotion is handled through the --workspace flag rather than a dedicated release-pipeline concept: npx rayfin switch --workspace <name> repoints your local project at a different target workspace (dev, test, production), and each subsequent rayfin up deploys there. There's no built-in staged-rollout or blue-green mechanism yet, treat this as a preview gap to design around rather than a solved problem.
Local Development Workflow
The Rayfin CLI and a local Docker stack are the backbone of iteration speed. A new project is scaffolded with:
npm create @microsoft/rayfin@latest -- "my-app" --template todoapp --workspace <workspace>
That pulls a starter template already wired to a target Fabric workspace. From the generated project directory, npm run dev brings up the full stack locally against Docker: schema, GraphQL API, and auth all running on your machine, with no dependency on a live Fabric deployment for the inner development loop. Microsoft explicitly positions GitHub Copilot as the intended pairing tool for writing entity and business-logic code inside this loop.
Creating Your First Microsoft Fabric App: Full Walkthrough
With the Fabric Apps workload enabled tenant-wide and a Fabric-capacity-backed workspace available, the path from zero to a deployed app is:
npm create @microsoft/rayfin@latest -- "my-app" --template todoapp --workspace <workspace>cd into the generated project folder. The scaffold produces a React + TypeScript frontend already wired to the Rayfin SDK, plus a rayfin/ folder holding your backend definition.rayfin/data/, then register each new entity in rayfin/data/schema.ts so the client generator produces typed proxies for it. Forgetting this step causes silent failures.npm run dev to bring up the full stack locally via Docker — schema, GraphQL API, and auth all on your machine. No live Fabric deployment needed for the inner development loop.npx rayfin up to deploy schema, API, auth configuration, and frontend together to Fabric in a single command. This provisions all four child services inside your workspace.Integration with Other Fabric Services
The OneLake-native design is what makes Fabric Apps interoperate with the rest of the platform without custom plumbing. Data an app writes is queryable from a Lakehouse or Warehouse SQL endpoint immediately, usable inside a notebook for downstream transformation or model training, and available to a Power BI semantic model as a DirectLake or import source - all without a scheduled extract. For Real-Time Intelligence scenarios, an app's write path can feed an Eventstream if the use case calls for near-real-time downstream processing. This is the practical reason a solution architect would choose Fabric Apps over an external Postgres-backed app for anything whose output needs to show up in existing BI or data engineering workflows.
Business Use Cases
Microsoft frames four patterns as the primary fit for Microsoft Fabric Apps. Rapid prototyping an idea going from a TypeScript file to a live, authenticated URL in minutes rather than after an infrastructure request. Internal tools and admin dashboards - approval queues, data-quality override screens, support-triage tools that need real write access and real auth, not just a report. Data exploration apps a custom frontend querying Fabric data via GraphQL when a standard Power BI report doesn't fit the interaction model. And AI agent backends, a governed, persistent-state data layer an agent can read and write against, rather than stateless function calls with no memory. Equally important: Microsoft is explicit that anything needing complex multi-step transactions, stored procedures, or a non-Entra-ID identity provider is out of scope today.
Microsoft Fabric Apps vs Traditional Web Dev, Power Apps, and Power BI Apps
The name invites confusion with adjacent tools, so it's worth being precise. A traditional web stack gives total control at the cost of building auth, hosting, and governance yourself. Power Apps is Microsoft's low-code platform for business users, typically reaching Fabric data through Dataverse virtual tables rather than owning the data directly. A "Power BI app" is an unrelated, older concept, a curated bundle of reports published to a workspace audience; it involves no backend logic at all. Fabric Apps sits between the first two: code-first and TypeScript-native, but with the infrastructure plumbing generated rather than hand-built.
| Dimension | Traditional Web Dev | Power Apps | Microsoft Fabric Apps |
|---|---|---|---|
| Primary audience | Professional developers | Business users / citizen developers | TypeScript developers & AI coding agents |
| Data location | Wherever you provision it | Dataverse (virtual tables into Fabric) | Native OneLake / Fabric SQL database |
| Backend effort | You build auth, hosting, DB from scratch | Largely abstracted, low-code | Generated from TypeScript decorators |
| Authentication | Any provider you integrate | Microsoft Entra ID / Dataverse security | Fabric SSO (Entra ID) only in production |
| Transactions / stored procs | Full support | Supported via connectors/flows | Not supported — app-layer compensation needed |
| Maturity (mid-2026) | GA, mature ecosystem | GA, mature ecosystem | Public preview — not for production-critical workloads yet |
Best Practices, Common Mistakes, and Preview Limitations
Best practices
Keep foreign keys and relationship decorators explicit only where your code actually reads or writes them let the generator handle the rest. Push every schema change through code and rayfin up db apply, never through the portal's query editor. Treat @role() policies as mandatory on any entity holding user-specific data, written and reviewed at the same time as the entity itself, not bolted on afterward. Design around the absence of transactions from day one write idempotent, compensable operations rather than assuming multi-step writes are atomic.
Common mistakes
Editing the SQL database schema directly in the portal is the single most common cause of a broken subsequent deploy. Forgetting to register a new entity in schema.ts produces a client method that silently doesn't exist rather than a clear compile error. Shipping secrets or connection strings inside static frontend assets is a real risk, since that content is public by default. Assuming totalCount or count() exist on the client produces broken pagination UI late in development.
Current limitations (Preview)
Composite primary keys and custom key column names aren't supported. Many-to-many relationships require an explicit join entity. There's no count() method and totalCount isn't populated on paginated results. Authentication is locked to Fabric SSO in production, with no custom identity provider option. Complex multi-step transactions and stored procedures aren't supported. Regional availability isn't universal yet check Fabric's supported-regions documentation before committing a project to a specific tenant region.
Performance, Monitoring, and Maintenance
Fabric Apps draws from the same capacity unit (CU) pool as your Warehouses, Lakehouses, and notebooks, there's no separate billing surface, which simplifies cost tracking but also means an inefficient app competes with other workloads on the same capacity. Monitor consumption through the Fabric Capacity Metrics app rather than estimating it. On the query side: select only the fields you render, always paginate instead of fetching full tables, and pull related-entity fields into the same query via dotted paths instead of a per-row second round trip. Reference data that changes rarely is worth caching client-side rather than re-querying on every render, and any operation you know is transaction-sensitive should be logged with enough context to reconcile manually.
@entity(), @text(), @uuid(), @one()/@many(), and @role().@role() policies; item-level permissions (Run and interact / Edit / Reshare) sit on top and don't inherit from workspace roles automatically.count()/totalCount on paginated queries.Frequently Asked Questions
Is Microsoft Fabric Apps generally available? No - it entered public preview at Microsoft Build 2026 (June 2, 2026), carrying supplemental preview terms, best-effort support, and no SLA. Hold production-critical workloads for GA.
Do I need to write GraphQL by hand? No. The generated RayfinClient exposes a fluent, type-safe API - select(), where(), orderBy(), that builds the query for you; the raw schema is still introspectable if you need it for a non-JS client.
Can I bring my own identity provider? No. Production apps authenticate exclusively through Fabric SSO (Microsoft Entra ID); email/password exists only for local development.
Can I run multi-step transactions? Not natively in preview model any operation that needs atomicity across multiple writes as an application-layer compensation pattern instead.
Where does application data physically live? In a Fabric-managed SQL database backed by OneLake, inside your own tenant and workspace - not in a third-party cloud account.
Conclusion: Should You Build with Microsoft Fabric Apps?
Microsoft Fabric Apps closes a real architectural gap: the application layer that data teams have always built outside the governed estate, one way or another. For a developer, the appeal is a single TypeScript codebase generating a full backend. For an architect, the appeal is that the resulting data never leaves the estate you already govern. The tradeoffs are real too, no transactions, no custom auth, no composite keys and worth treating as hard constraints in a scoping conversation rather than details to discover mid-build.
Numlytics works with engineering teams evaluating Microsoft Fabric migration and platform architecture who need to weigh new capabilities like Fabric Apps against existing investments in Power BI, Power Apps, and custom services. If you're scoping a pilot, talk to a certified Fabric consultant before committing a production workload to a preview feature.