fayzfayz sdk
PTEN

Concepts

Six concepts make every Fayz app click into place. Master these and you can read any Fayz project without getting lost: the plugin and its manifest, the multi-tenant core library, providers, the two paths to building, version channels and incubator plugins. Each one gets a paragraph and, where it helps, a snippet of code.

1. Plugin and manifest

A plugin is a packaged capability — scheduling, CRM, finance. You create it by calling its factory (createAgendaPlugin, createCrmPlugin, …), which returns a manifest: a description, in data, of everything that plugin contributes to the app. Routes, navigation, dashboard widgets, database migrations, AI tools — all declared in one object, not scattered across the code.

import { createAgendaPlugin } from '@fayz-ai/plugin-agenda'

const agenda = createAgendaPlugin()
// → { id, name, icon, version, navigation, routes, widgets?,
//     dashboardWidgets?, migrations?, ... }  = PluginManifest

The app composes a list of these manifests and the runtime does the rest: it builds the navigation, resolves the routes, registers the widgets and orders the migrations. The rule that keeps this sound: a plugin emits nothing app-specific by default — whatever it contributes to a shared surface is opt-in. The app decides what shows up.

2. Multi-tenant core library

Every Fayz app sits on the core library (formerly called saas_core): the base tables every business needs — tenants, people, tenant_members, role_permissions, appointments, transactions. These tables don't live in a separate core. schema — they all live in public.*. On top of them, the model has just two more layers:

public.*      shared base tables (tenants, people, tenant_members, role_permissions, appointments…)
plg_*         tables a plugin installs and owns (plg_shop_products, plg_courses_*…)
your tables   whatever your app/incubator creates on top

A plugin installs its own tables with the plg_ prefix and reuses the core tables it needs instead of copying them; if finance needs to know "who got paid", it references public.people rather than keeping a copy. One source of truth per entity.

The tenant is your SaaS customer (a salon, a clinic). Every table — core, plugin or your own — carries tenant_id, and isolation is enforced by Postgres RLS (Row-Level Security) keyed on that tenant_id. RLS is the boundary, not schema separation: you don't filter by tenant in the application — the database refuses rows from another tenant. You bring your own Supabase (BYOS) and it gets exactly this shape; it's the same one the platform hosts at scale. Full details in Data → Model and RLS and multi-tenancy.

3. Providers (mock-first)

A provider is a plugin's backend abstraction: same interface, two implementations. Every app starts with backend.provider: "mock" — sample data, simulated auth, zero environment variables — so you can build and demo without touching infrastructure. When you're ready, you flip: switch the provider to supabase and the same plugin starts talking to real Postgres.

// app.manifest.json — mock to real is one line:
"backend": { "provider": "mock" }
"backend": { "provider": "supabase", "projectRef": "<your-ref>" }

At the plugin level this shows up as a safe provider that picks Supabase when credentials are present and falls back to mock when they aren't — the same interface on both sides, so nothing else in the plugin changes on the flip. How to connect a real Supabase is in Data → Supabase.

4. The two paths

There are two ways to build. With the Fayz App you get a complete admin shell ready to go — generated navigation, tenant switching, permissions, CRUD — straight from your config. With Fayz Headless you consume the core and the plugin bundles inside your own site, owning the shell yourself. Both use the same plugins and the same backend; what changes is who assembles the interface around them.

Picking the right path up front saves rework — the full comparison, with when to use each, is in The two paths.

5. Version channels

Two different things use similar names, so keep them apart:

  • Release channels (stable, latest, preview) are sets of versions — coherent groups of @fayz-ai/* packages you install together. fayz create pins your app to the stable channel by default.
  • Maturity tiers (stable, beta, preview, internal) are a property of each package — how much you can lean on it today.

Be honest with yourself: nothing is tier stable yet. Fayz is pre-1.0, and while a package is on 0.x, a minor bump (0.4.00.5.0) can carry breaking changes — and we use that room. The core and the plugins already running day to day are tier beta. Pin exact versions in production and read the changelog before upgrading.

The full tier table, with versioning guarantees and bug response times, is in Reference → versions and channels.

6. Incubator plugins

Not every capability needs to become a published package. An incubator plugin is a plugin that lives inside your own app (in src/plugins/), following the same contract as the official plugins — same manifest, same routes, migrations and providers. It's where you put logic specific to your product, or incubate a new plugin before eventually graduating it to the catalog.

fayz create plugin my-plugin

Because the contract is identical, an incubator plugs into the SDK's UI exactly like an official plugin — the only difference is who owns the code. The patterns for writing one are in Building plugins → incubator.

Next step