Skip to main content

ADR-0005: Use Refine for Frontend (Studio & Desk)

  • Status: Proposed
  • Date: 2026-01-07
  • Deciders: @anshpansuriya14
  • Supersedes: N/A
  • Superseded by: N/A

Context

Framework M requires a frontend solution for two distinct components:

ComponentPurposePhase
StudioVisual DocType builder that generates Python codePhase 07
DeskGeneric admin UI rendering forms/lists from metadataPhase 09

Forces at play:

  • Architectural Alignment: Frontend must follow our Ports & Adapters pattern (ARCHITECTURE.md Sec 3.2)
  • Metadata-Driven: Backend generates JSON Schema, frontend renders forms dynamically (Sec 5.4)
  • UI Flexibility: Support for "Progressive Customization" — Indie (default UI) → Plugin (extend) → Eject (replace)
  • Stack Preference: Phase 09 specifies Tailwind CSS + shadcn/ui as the UI layer
  • No Vendor Lock-in: Must be fully open-source with no paid enterprise tier for core features
  • TypeScript-first: Type safety is a core principle (CONTRIBUTING.md)

Frameworks Evaluated:

FrameworkStarsLicenseFocus
Refine30k+MITHeadless CRUD meta-framework
React Admin25k+MIT (Enterprise paid)Material UI admin framework
AdminJS8k+MITNode.js backend admin generator
Tremor17k+Apache 2.0Dashboard/analytics components
RJSF14k+BSD-3JSON Schema form renderer

Decision

We will use Refine as the primary frontend framework for Studio and Desk, with shadcn/ui for components, RJSF for schema-driven forms, and Tremor for dashboards.

Full Stack:

LayerTechnologyPurpose
Data/StateRefine CoreCRUD, caching, auth, access control
FormsRJSF + react-hook-formSchema-driven form rendering
Tables@tanstack/react-tableFlexible data tables
UI Componentsshadcn/uiAccessible, Tailwind-based components
StylingTailwind CSSUtility-first styling
ChartsTremorDashboard analytics
Code EditorMonaco EditorStudio code preview
Drag & Drop@dnd-kitStudio field editor

Deployment Configuration (Base URLs):

Different deployments require different URL configurations:

ScenarioUI BaseAPI BaseConfig
Same Origin (Default)//api/v1No config needed
Subdirectory/portal/apiAPI_BASE_URL=/api
Separate Subdomainsportal.example.comapi.example.comAPI_BASE_URL=https://api.example.com
CDN for UIcdn.example.com/appapi.example.comFull URL in config

Configuration via window.__FRAMEWORK_CONFIG__ or environment:

// Injected by backend or build-time env
window.__FRAMEWORK_CONFIG__ = {
apiBaseUrl: "/api/v1", // Default: same origin
metaBaseUrl: "/api/meta", // Metadata endpoint base
wsBaseUrl: "/api/v1/stream", // WebSocket base
};

Data provider uses configured base URL:

const config = window.__FRAMEWORK_CONFIG__ || { apiBaseUrl: "/api/v1" };

const dataProvider = frameworkMDataProvider(config.apiBaseUrl);
// fetch(`${config.apiBaseUrl}/${resource}`) → configurable

Why Refine aligns with Framework M:

Framework M (Python)              Refine (TypeScript)
─────────────────────────────────────────────────────
RepositoryProtocol ←→ DataProvider
PermissionProtocol ←→ AccessControlProvider
AuthContextProtocol ←→ AuthProvider
EventBusProtocol ←→ LiveProvider
DocType ←→ Resource
BaseController ←→ Resource hooks

Consequences

Positive

  • Architectural Symmetry: Refine's Provider pattern is identical to our Ports & Adapters
  • Headless Design: Enables all three customization modes (Indie, Plugin, Eject)
  • Metadata-Driven: Native useForm + RJSF integration for GET /api/meta/{doctype}
  • No Vendor Lock-in: MIT license, all features are free
  • Stack Compatibility: Works seamlessly with Tailwind + shadcn/ui
  • Active Community: 30k+ stars, excellent documentation, regular releases

Negative

  • Custom Data Provider: Requires ~100 lines of boilerplate (well-documented pattern)
  • No Built-in Guessers: Unlike React Admin, no auto-generated UI (mitigated by RJSF)
  • Learning Curve: Team needs to learn Refine's hook-based API

Neutral

  • TypeScript-first (aligns with our preference)
  • React Query under the hood for caching

Alternatives Considered

OptionProsCons
Chosen: RefineHeadless, provider pattern matches our architecture, MIT, works with any UIRequires custom data provider
React AdminFast to start, guessers auto-generate UI, large ecosystemMaterial UI lock-in, paid enterprise features, conflicts with Tailwind
AdminJSAuto-generates admin from ORMNode.js only — incompatible with Python/Litestar
TremorExcellent charts, Tailwind-nativeDashboard-focused, not a complete admin framework
RJSFPerfect for JSON Schema formsForm library only, not a full stack

Why React Admin was rejected:

Framework M PrincipleReact Admin Conflict
UI-agnostic (Sec 2)Locked to Material UI
Tailwind/shadcn stack (Phase 09)Material UI incompatible
No vendor lock-inEnterprise features (RBAC, audit) are paid
Additive DesignDifficult to swap UI layer without full rewrite

Why AdminJS was rejected:

  • Designed exclusively for Node.js backends (Express, NestJS, Fastify)
  • Requires tight ORM integration (TypeORM, Prisma)
  • Does not work with Python/Litestar

References