
Fusion Developer App
- 814 installs
- 1 repo stars
- Updated August 4, 2026
- equinor/fusion-skills
fusion-developer-app is an Equinor agent skill that provides framework-aware guidance for adding features, components, hooks, and API wiring in Fusion Framework React applications.
About
fusion-developer-app is an MIT-licensed skill from equinor/fusion-skills for building features inside Fusion Framework React applications. It guides app-scoped framework research so developers pick the correct hooks, modules, packages, and integration patterns before writing code. Use it when adding components or pages, creating hooks and services, wiring API endpoints, or extending Fusion module configuration. The skill explicitly excludes issue authoring, CI/CD setup, backend service changes, and general documentation not tied to app implementation.
- Guides feature development using correct Fusion Framework hooks, modules, and integration patterns
- Performs app-scoped framework research before suggesting implementation choices
- Handles component, hook, service, page, and API endpoint tasks
- Requires a bootstrapped Fusion Framework React app with EDS and fusion-react packages
- Depends on fusion-research and fusion-code-conventions skills
Fusion Developer App by the numbers
- 814 all-time installs (skills.sh)
- +21 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #437 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/equinor/fusion-skills --skill fusion-developer-appAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 814 |
|---|---|
| repo stars | ★ 1 |
| Last updated | August 4, 2026 |
| Repository | equinor/fusion-skills ↗ |
How do you add features in Fusion Framework React apps?
Get guided, framework-aware assistance when adding features, components, hooks, or API wiring inside Equinor Fusion Framework React applications.
Who is it for?
Developers building or extending Equinor Fusion Framework React applications who need correct hooks and module patterns.
Skip if: Non-Fusion React projects, backend-only services, CI/CD configuration, or general Equinor docs outside app code.
When should I use this skill?
User builds Fusion Framework React features, wires APIs, or asks which Fusion hook or module to use.
What you get
Fusion-aligned components, hooks, services, pages, and module configuration with correct API endpoints.
- React components
- Hooks and services
- API wiring
Files
Fusion App Development
When to use
Use this skill when developing features, components, hooks, services, or types for a Fusion Framework React application.
Typical triggers:
- "Add a component / hook / service / page for ..."
- "Wire up the API"
- "Configure a Fusion module"
- "Which Fusion Framework hook / package should this app use?"
- "Persist this preference as an app setting"
- "Add bookmark / analytics / feature flag support"
- "Add a chart / people picker / person column to AG Grid"
- "How do I write a custom Fusion Framework module?"
- "Should this be a Fusion module or a React context?"
Implicit triggers:
- Building in
src/ - References Fusion Framework modules, EDS,
@equinor/fusion-react-*, or styled-components - References app settings, bookmarks, analytics,
app.config.ts - Adding route, page, or data-fetching layer
- References charting:
@equinor/fusion-framework-react-ag-charts,chart.js,react-chartjs-2
When not to use
Do not use this skill for:
- Issue authoring/triage →
fusion-issue-authoring - Skill authoring →
fusion-skill-authoring - Backend/service changes (separate repo)
- CI/CD or deployment config
- Architecture docs (use ADR template)
For Fusion Framework package ownership, hook behavior, or example discovery → use fusion-research first.
Required inputs
Mandatory
- What to build: feature, component, hook, or service description
- Where it fits: layer (component, hook, service, type) and parent/sibling context
For ambiguous requests, consult assets/follow-up-questions.md before implementing.
Conditional
- API endpoint details when the feature involves data fetching
- Design/layout specifics when building visual components
- Fusion module name when extending module configuration
- Whether state should persist per-user, be shareable via bookmark, or stay runtime-only
Instructions
Step 1 — Discover project conventions
Inspect target repo before writing code:
1. Read package.json — package manager (bun/pnpm/npm), scripts, dependencies. 2. Read tsconfig.json — TypeScript settings, path aliases. 3. Scan src/ — directory layout, layer structure. 4. Check docs/adr/ or contribute/ for project-specific code standards. 5. Check formatter/linter config (biome.json, .eslintrc, prettier). 6. Read app.config.ts and app.manifest.ts — endpoints, environment setup. 7. Delegate uncertain Fusion Framework behavior, package ownership, or cookbook examples to fusion-research before writing code.
Adapt to discovered conventions. references/ patterns are defaults — defer to project-specific rules when they differ.
Step 2 — Plan the implementation
New app from scratch → use assets/new-app-checklist.md.
1. Break into discrete files/changes. 2. Map to correct directory. Typical Fusion app:
src/components/— React components (presentation layer)src/hooks/— Custom React hooks (state and side-effect logic)src/api/— API clients, data transforms, business logicsrc/types/— TypeScript interfaces, type aliases, enumssrc/routes.ts— Route definitions (when using Fusion Router)src/config.ts— Fusion module configurationsrc/App.tsx— Root component, layout shell
3. Identify shared types early — define before referencing. 4. Project uses routing → follow references/using-router.md for DSL + page patterns. 5. Different structure → follow it.
Step 3 — Implement following code conventions
Follow project code standards from Step 1. For naming, TSDoc, inline comments, type patterns, code style, error handling → defer to fusion-code-conventions.
For convention questions during implementation, invoke fusion-code-conventions directly.
Step 4 — Style with styled-components, EDS, and Fusion React components
Follow references/styled-components.md, references/styling-with-eds.md, references/using-fusion-react-components.md:
- Use
styled-componentsfor custom styling (Fusion convention). - No CSS Modules, global CSS, Tailwind, or alternative CSS-in-JS unless project uses them.
- Use
Styledobject pattern for co-located styled components. - Prefer EDS (
@equinor/eds-core-react) for standard UI. - Use EDS design tokens (
@equinor/eds-tokens) for colors, spacing, typography. - Extend EDS with
styled()for customization. - Use
@equinor/fusion-react-*for domain needs not in EDS (person display/selection, side sheets, progress). - Inline
styleprops: one-off tweaks only. - For page/view structure (shell composition, layout zones, empty/loading states), invoke
agents/design.md. For component-level EDS styling, invokeagents/styling.md.
Step 5 — Wire up data fetching (when applicable)
Follow references/configure-services.md, references/using-react-query.md, references/configure-mocking.md:
- Register HTTP clients via
configureHttpClientinconfig.tsorapp.config.ts. - Access clients with
useHttpClient(name)from@equinor/fusion-framework-react-app/http. - *Prefer `@equinor/fusion-framework-react-app/
hooks** over direct module access. Reserveframework.modules.*` for non-React contexts. - React Query: wrap
useQueryin thin custom hooks. - Query keys: derived from API path + parameters.
- Keep client UI state in React state/context, not server-state libs.
Step 6 — Configure Fusion modules (when applicable)
Identify which module the user needs, then read only the matching reference:
| Need | Reference |
|---|---|
| HTTP clients / API integration | references/configure-services.md |
| Context module | references/using-context.md |
| Router and pages | references/using-router.md |
| AG Grid | references/using-ag-grid.md |
| AG Charts (standalone) | references/using-ag-charts.md |
| AG Grid integrated charts | references/using-ag-grid-charts.md |
| EDS + Fusion React components | references/using-fusion-react-components.md |
| People service (search, display, pick) | references/using-people-service.md |
| Settings | references/using-settings.md |
| Bookmarks | references/using-bookmarks.md |
| Analytics | references/using-analytics.md |
| Runtime config / environment | references/using-assets-and-environment.md |
| Feature flags | references/using-feature-flags.md |
| General framework modules | references/using-framework-modules.md |
| Custom module authoring | references/using-custom-modules.md |
- Module setup in
config.tsviaAppModuleInitiatorcallback. - Access modules via hooks:
useAppModule,useHttpClient,useCurrentContext. - Register HTTP endpoints in
app.config.tsfor new API integrations. - Enable navigation with
enableNavigationinconfig.tswhen app uses routing. - Define routes via Fusion Router DSL (
layout,index,route,prefix) for auto code splitting. - Unclear framework API → use
fusion-researchbefore choosing implementation pattern.
Step 7 — Validate
Use assets/review-checklist.md as post-generation checklist.
1. Run typecheck (bun run typecheck or pnpm typecheck) — zero errors. 2. Run lint/format check — zero violations. 3. Every new exported symbol has TSDoc. 4. Styling follows project conventions. 5. No new dependencies unless justified/approved.
Expected output
- New/modified
src/files following project layer structure. - All files pass typecheck + lint.
- Every exported function, component, hook, and type has TSDoc.
- Styling follows project conventions.
- Brief summary of what changed and why.
Helper agents
Optional helpers in agents/. Use for focused review or mid-implementation guidance. Runtimes without skill-local agents apply criteria inline.
Companion skill: fusion-research for source-backed Fusion ecosystem research when implementation is blocked by uncertainty.
- `agents/framework.md` — Fusion Framework integration: modules, HTTP clients, bootstrap, runtime config, settings, bookmarks, analytics. Prefers `mcp_fusion_search_framework`; falls back to
mcp_fusion_search_docs. Consult when wiringconfig.ts,app.config.ts, or framework module access. - `agents/styling.md` — EDS component selection, styled-components, design tokens, accessibility. Prefers `mcp_fusion_search_eds`. Consult when building/modifying visual components.
- `agents/design.md` — page/view structure: Fusion Portal shell composition, layout zones, side panel usage, empty/loading state patterns. References
equinor-design-systemfor layout ground truth. Delegates component-level checks toagents/styling.md. Consult when scaffolding new pages or layout wrappers. - `agents/data-display.md` — AG Grid vs AG Charts, module setup, column defs, chart options, integrated charting. Prefers `mcp_fusion_search_framework`. Consult for grids, charts, dashboards. Use
assets/charts-decision-matrix.mdfor library selection. - `agents/person-components.md` —
@equinor/fusion-react-person:PersonAvatar,PersonCard,PersonListItem,PersonPicker,PeoplePicker,PeopleViewer,PersonCell(AG Grid). DOM event pattern, valueGetter setup, pitfalls. Consult for any person display, search, or selection UI. - `agents/code-quality.md` — delegates convention checks (naming, TSDoc, TS strictness, intent comments) to
fusion-code-conventions, aggregates findings. Run on every new/modified file before finalizing.
Safety & constraints
- No new dependencies without explicit approval.
- No direct DOM manipulation — use React patterns.
- No
anytypes — TypeScript strict mode standard. - No secrets or credentials in source files.
- Conventional commits (
feat:,fix:,refactor:, etc.). - No infrastructure files (docker-compose, CI config) unless explicitly asked.
Code Quality Agent
Role
Use this helper agent to review Fusion Framework app code against quality standards. Delegates to the fusion-code-conventions skill for all convention rules — do not duplicate rule logic here.
Inputs
file_paths: source files to reviewproject_standards: path to project-specific code standards (contribute/,.github/copilot-instructions.md), if available
Process
Step 1 — Discover project standards
1. Check for project-specific overrides: contribute/, .github/copilot-instructions.md, AGENTS.md. 2. Check for formatter/linter config: biome.json, .editorconfig. 3. Project-specific rules take precedence over skill defaults.
Step 2 — Delegate to fusion-code-conventions
Invoke the fusion-code-conventions skill on each file. It will activate the appropriate language agents in parallel:
- TypeScript and React agents for convention, naming, TSDoc, type system, and code style
- Intent agent for inline comment quality across all files
- Constitution agent when the project has ADRs or contributor docs
Do not reimplement convention rules here — fusion-code-conventions is the authoritative source.
If `fusion-code-conventions` is not available: Prompt the user to install it before continuing:
"fusion-code-conventions is required for a full convention review but does not appear to be installed. Install it with:```
npx -y skills add fusion-skills --skill fusion-code-conventions --agent github-copilot
```
Once installed, re-run this review for complete coverage including ADR and contributor-doc enforcement.
Alternatively, confirm you'd like a partial review using assets/review-checklist.md (covers TypeScript, TSDoc, naming, and code quality — but not constitution checks)."Do not silently fall back to the checklist without offering the install option first.
Step 3 — Report findings
Aggregate and surface findings from fusion-code-conventions in context of the Fusion app:
- Required — must fix before merge: TSDoc missing on exports,
anytypes, naming violations, constitutional violations - Recommended — should fix: weak intent comments, undocumented magic values, unjustified suppressions
- Advisory — consider: missing decision records, stale ADRs, refactoring opportunities
Include Fusion-specific context where relevant (e.g. a naming violation on a hook that wraps useHttpClient).
Data Display Agent
Role
Use this helper agent to review or advise on data display implementation — choosing between AG Grid (tabular) and AG Charts (visual), configuring either surface, combining them on dashboard-style pages, and ensuring the data pipeline from API to rendered output follows Fusion Framework patterns.
Inputs
file_paths: source files to review (grid/chart components,config.ts, data hooks)question: specific data display question, if anydata_shape: the API response or data structure being displayed, if known
MCP tooling
When the Fusion MCP server is available, prefer `mcp_fusion_search_framework` to look up AG Grid and AG Charts packages, cookbook examples, and module configuration. This is more reliable than relying on memory alone.
Example queries:
mcp_fusion_search_framework→"enableAgGrid AgGridReact useTheme fusion-framework-react-ag-grid"mcp_fusion_search_framework→"ColDef columnDefs defaultColDef ag-grid column definition"mcp_fusion_search_framework→"fusion-framework-react-ag-charts AgCharts AgChartOptions"mcp_fusion_search_framework→"charts cookbook bar line pie area visualization"mcp_fusion_search_framework→"IntegratedChartsModule AG Grid charts enableAgGrid"mcp_fusion_search_framework→"AgChartsEnterpriseModule enterprise chart types"mcp_fusion_search_eds→"Table EDS simple read-only table"
Process
Step 1: Determine the data display approach
Decide which surface fits the task:
| Need | Surface | Reference |
|---|---|---|
| Sortable, filterable, editable tabular data | AG Grid | references/using-ag-grid.md |
| Simple read-only table, few rows | EDS Table | references/using-fusion-react-components.md |
| Charts from standalone data (bar, line, pie, area) | AG Charts | references/using-ag-charts.md |
| Ad-hoc charts created from grid data | AG Grid integrated charts | references/using-ag-grid-charts.md |
| Lightweight prototype chart | Chart.js | references/using-ag-charts.md (alternative section) |
| Dashboard with both grid and charts | AG Grid + AG Charts | combine references |
When the user says "display this data" without specifying a format, ask whether tabular or visual is the primary view. Use assets/charts-decision-matrix.md when the chart library choice is unclear.
Step 2: Verify setup
For AG Grid:
@equinor/fusion-framework-react-ag-gridis installed.enableAgGrid(configurator)is called inconfig.ts.- Feature modules are registered via
builder.setModules()(since AG Grid 33). useTheme()provides the Fusion/EDS theme to<AgGridReact theme={theme} />.
For AG Charts standalone:
@equinor/fusion-framework-react-ag-chartsis installed.ModuleRegistry.registerModules([AllCommunityModule])is called once at application startup.- Enterprise import from
/enterprisesub-path only when enterprise chart types are needed.
For AG Grid integrated charts:
- Both
@equinor/fusion-framework-react-ag-gridand@equinor/fusion-framework-react-ag-chartsare installed. IntegratedChartsModule.with(AgChartsEnterpriseModule)is included in theenableAgGridmodule list.- Enterprise license is available (integrated charts require enterprise).
Step 3: Review AG Grid implementation
When reviewing grid code, check:
- Imports: come from
@equinor/fusion-framework-react-ag-grid, not directly fromag-grid-*. - Theming:
useTheme()is used, not inline theme objects or raw CSS overrides. - Column definitions: typed with
ColDef<T>[], usingfield+headerNameat minimum. - Default column defs: shared defaults (
resizable,filter,flex,sortable) reduce repetition. - Value formatters: dates, numbers, and enums use
valueFormatter, not string interpolation in cell renderers. - Cell renderers: custom render logic uses
cellRenderer, kept minimal. - Row data typing:
rowDataprop is typed, notany[]. - Module registration: only needed modules are registered (tree-shaking).
Step 4: Review chart implementation
When reviewing chart code, check:
- Imports: AG Charts imports come from
@equinor/fusion-framework-react-ag-charts, not directly fromag-charts-*. - Data typing: chart data has a typed interface, not inline untyped objects.
- Options typing: AG Charts uses
AgChartOptions; Chart.js uses the library's option types. - Separation of concerns: data fetching, data transformation, and rendering are in separate layers.
- Responsive sizing: AG Charts uses container-based sizing; Chart.js uses
responsive: truewith a sized wrapper. - Labels and legends: charts have meaningful axis labels, series names, and tooltips.
- Color usage: prefers EDS design tokens or a consistent palette over random hex values.
- Declarative options: AG Charts uses
AgChartOptionsobjects, not imperative API calls. - Reactive updates:
useState<AgChartOptions>for chart options that change.
Step 5: Review combined grid + chart pages
When a page has both grid and chart:
- Data is fetched once and shared, not fetched separately for each display.
- Grid and chart use the same typed data model.
- If the chart shows a summary of grid data, the transform is explicit and tested.
- Layout uses EDS spacing tokens and responsive containers.
Step 6: Report findings
Produce a concise list:
- Correct: patterns following data display conventions
- Issues: problems with specific fix recommendations
- Suggestions: surface alternatives, data flow improvements, accessibility notes
Reference references/using-ag-grid.md, references/using-ag-charts.md, and references/using-ag-grid-charts.md for the canonical patterns.
Design Agent
Role
Review or advise on page and view structure in a Fusion Portal app — shell composition, layout zone nesting, empty/loading state patterns, side panel usage, and structural anti-patterns conflicting with the Fusion Portal shell.
Does not review individual component token usage or EDS component selection — delegate those to agents/styling.md.
Design ground truth comes from equinor-design-system system skill. When installed, consult for authoritative token names, layout zone conventions, and empty/loading state patterns.
Inputs
file_paths: component or page-level files to review (e.g. top-level route components, layout wrappers,App.tsx)question: specific structural or layout question, if any
MCP tooling
- Use `mcp_fusion_search_eds` for EDS component questions: tokens, props, usage examples, and layout primitives (e.g.
Typography,Button,Icon,Progress,TopBar). - Use `mcp_fusion_search_framework` for Fusion-specific component and shell questions (e.g.
SideSheetfrom@equinor/fusion-react-side-sheet, how portal wraps the app, navigation zones, module APIs).
Process
Step 1: Identify structural scope
Read target files. Determine: 1. Root/layout component (App.tsx, top-level route) or leaf component? 2. Establishes layout containers (flex, grid, scrollable wrappers)? 3. Uses side panel or overlay? 4. Defines loading or empty states?
Focus structural review on root and near-root components. Leaf components rarely have structural issues.
Step 2: Check shell composition
Verify app does not replicate portal-owned zones:
- No custom top navigation bar — Fusion Portal owns global header. Flag any custom fixed/sticky header at viewport top.
- No custom left rail or sidebar navigation — portal shell owns left rail.
- No outer margin/padding on root app element — portal provides content inset; extra outer spacing creates double-guttering.
- No fixed full-viewport-height container — allow content to stretch naturally; do not hard-code
height: 100vhon app root.
Step 3: Check layout zone usage
Verify layout follows three-zone convention from equinor-design-system:
| Zone | Expected | Anti-pattern |
|---|---|---|
| Main content | <main> or app root <div> spanning full available area | Custom flexbox wrapper that clips or limits available height |
| Side panel | @equinor/fusion-react-side-sheet | Custom position: fixed or position: absolute right panel |
| Spacing | --eds-spacing-* / --eds-container-space-* tokens | Arbitrary margin: 24px, padding: 16px raw pixel values |
For spacing violations, identify specific token (refer to equinor-design-system spacing table or mcp_fusion_search_eds):
var(--eds-spacing-horizontal-sm)/var(--eds-spacing-vertical-sm)— tight gaps (8px)var(--eds-container-space-horizontal)/var(--eds-container-space-vertical)— container paddingvar(--eds-page-space-horizontal)/var(--eds-page-space-vertical)— page-level padding
Step 4: Check structural anti-patterns
Flag:
- Nested full-page scrollable containers — page content wrapped in
overflow-y: autowhen browser scroll is intended. Deliberate AG Grid or fixed-height table regions are acceptable. - Custom positioned overlays instead of SideSheet —
position: fixedright-side panels, custom drawers, or dialog-like components where@equinor/fusion-react-side-sheetor EDSDialogshould be used. - Shadow or color outside EDS tokens — raw
box-shadow, hex codes, or named CSS colors instead of--eds-color-bg-*,--eds-color-text-*, and--eds-color-border-*tokens. EDS v2 has no elevation CSS variables; use EDSPapercomponent or JS token import for elevation. - Layout replicating EDS primitives — manual flex/grid containers duplicating EDS
Grid,Divider, orStack-like behavior.
For component-level token violations (button color, typography variant, icon size), delegate to agents/styling.md.
Step 5: Check empty and loading states
Verify correct state patterns:
| State | Expected pattern | Anti-pattern |
|---|---|---|
| Loading | EDS Progress.Circular or Progress.Dots (import { Progress } from '@equinor/eds-core-react') — centered in content area | Blank screen, spinner in corner, or display: none |
| Empty (no data) | Typography + optional primary action Button — centered or top-aligned | Omitted state (user sees nothing), custom illustration without text |
| Error | Typography with danger semantic color + retry action | Raw alert(), uncaught exception, or blank component |
If state missing entirely (component renders nothing when loading or empty), flag as structural gap.
Step 6: Report findings
Produce structured report:
Structure — shell composition and zone usage:
- ✅ Correct patterns
- 🚫 Issues (structural violations) with specific fix
Anti-patterns — things to remove or replace
Empty / loading states — present, missing, or incorrect
Delegated to `styling.md` — component-level EDS/token issues spotted but out of scope
If no issues, state: "Shell composition and layout zones follow Fusion Portal conventions."
Framework Agent
Role
Use this helper agent to review or advise on Fusion Framework integration — module configuration, HTTP clients, authentication, context, navigation, settings, bookmarks, analytics, runtime config, and the app bootstrap lifecycle.
When the question is mainly about framework package ownership, exact API behavior, or finding a supporting example, use the companion skill fusion-research first and then apply that evidence during the integration review.
Inputs
file_paths: source files to review (typicallyconfig.ts,index.ts,app.config.ts, hooks using framework modules)question: specific framework question or concern, if any
MCP tooling
When the Fusion MCP server is available, prefer `mcp_fusion_search_framework` to look up Fusion Framework APIs, module configuration, hooks, and package documentation. Use mcp_fusion_search_docs for general Fusion platform guidance (onboarding, concepts, operations). This is more reliable than relying on memory alone.
If the runtime supports companion skills, follow the fusion-research workflow for evidence gathering: choose the right search lane, capture metadata.source plus the supporting excerpt, and stop after one refinement pass when results stay weak.
Example queries:
mcp_fusion_search_framework→"configureHttpClient useHttpClient configure http module"mcp_fusion_search_framework→"renderApp makeComponent AppModuleInitiator entry point"mcp_fusion_search_framework→"useCurrentContext context module"mcp_fusion_search_framework→"useCurrentBookmark enableBookmark bookmark module"mcp_fusion_search_framework→"useAppSetting useAppSettings settings module"mcp_fusion_search_docs→"analytics useTrackFeature portal analytics"mcp_fusion_search_docs→"app configuration endpoints environment"
Process
Step 1: Read the framework surface
1. Read the project's src/config.ts to understand existing module configuration. 2. Read app.config.ts and app.manifest.ts for endpoint and environment setup. 3. Read src/index.ts to confirm the bootstrap pattern in use. 4. Identify which Fusion modules are already configured and which hooks are in use. 5. If the review is blocked on uncertain framework behavior, gather source-backed evidence via fusion-research before deciding whether the code is correct.
Step 2: Check against framework API
Validate that the code follows current Fusion Framework patterns:
- Entry point: uses
renderApp(App, configure)or the lower-levelmakeComponentpattern. - HTTP clients: registered via
configureHttpClientinconfig.tsor viaendpointsinapp.config.ts(auto-registered). - Client access: uses
useHttpClient(name)from@equinor/fusion-framework-react-app/http. Direct module access (framework.modules.http.createClient) is only acceptable in non-React contexts like route loaders. - Context: uses
useCurrentContext()from@equinor/fusion-framework-react-app/context. - Auth: uses
useCurrentAccount()/useAccessToken()from@equinor/fusion-framework-react-app/msal— never manages tokens manually. - Navigation: uses
useRouter()from@equinor/fusion-framework-react-app/navigation. - Environment variables: uses
useAppEnvironmentVariables()for runtime config. - Settings: uses
useAppSetting()/useAppSettings()for per-user preferences. - Bookmarks: uses
enableBookmark()in configuration anduseCurrentBookmark()for shareable view state. - Analytics: uses
useTrackFeature()for user-facing instrumentation.
Step 3: Identify issues
Flag:
- Duplicate HTTP client registrations (same client in both
app.config.tsandconfig.ts) - Missing MSAL scopes for authenticated endpoints
- Direct
fetch()calls that should use the Fusion HTTP client (misses auth, interceptors, observables) - Module access outside of React component context (hooks must be called inside components/hooks)
- Hardcoded URLs that should come from
app.config.tsendpoints or environment variables - New code using deprecated bookmark patterns where
useCurrentBookmark()is the better current surface - Shareable view state stored in app settings instead of bookmarks
- Custom telemetry calls where
useTrackFeature()would match framework analytics
Step 4: Report findings
Produce a concise list:
- Correct: patterns that follow the framework API properly
- Issues: problems with specific fix recommendations
- Suggestions: optional improvements (e.g. adding error boundaries, using environment variables)
Reference references/create-fusion-app.md, references/configure-services.md, references/using-framework-modules.md, references/using-settings.md, references/using-bookmarks.md, references/using-assets-and-environment.md, and references/using-analytics.md for the canonical patterns.
Person Components Agent
Role
Use this helper agent when a developer asks how to display, search, pick, or list people in a Fusion Framework app using @equinor/fusion-react-person. It covers component selection, usage patterns, PersonCell in AG Grid, common pitfalls, and the correct import paths.
Delegate token-level EDS styling questions to agents/styling.md. Delegate AG Grid module setup questions to agents/data-display.md.
Inputs
question: what the developer is trying to build (e.g. "show a person avatar", "ag-grid column with person names", "multi-person picker")file_paths(optional): source files already in the project to review for existing patterns
MCP tooling
When Fusion MCP is available, use `mcp_fusion_search_eds` for EDS component props and `mcp_fusion_search_framework` for framework integration questions. Cross-check against the fusion-react-components Storybook when component behaviour is unclear.
Process
Step 1: Identify the UI need
Map the developer's request to a component using the decision guide:
| UI need | Component |
|---|---|
| Avatar only, hover for details | PersonAvatar |
| Full detail view (card, panel, popover) | PersonCard |
| One-line person row in a list | PersonListItem |
| Person row with action button (remove, edit) | PersonListItem with children |
| Pick a single person | PersonPicker or PersonSelect |
| Pick multiple people | PeoplePicker |
| Display a selected collection | PeopleViewer |
| AG Grid cell | PersonCell |
If the need is still ambiguous after reading the request, ask: _"Are you looking to display a person's info, let the user pick a person, or show people in a data grid?"_
Step 2: Confirm import paths
All person components are imported from @equinor/fusion-react-person:
import {
PersonAvatar,
PersonCard,
PersonListItem,
PersonPicker,
PersonSelect,
PeoplePicker,
PeopleViewer,
PersonCell,
} from '@equinor/fusion-react-person';
import type {
PersonInfo,
PersonAddedEvent,
PersonRemovedEvent,
} from '@equinor/fusion-react-person';Step 3: Apply the usage example
Refer to references/using-fusion-react-components.md for complete copy-pasteable examples for each component.
Key patterns to reinforce:
Display components resolve people data automatically — PersonAvatar, PersonCard, PersonListItem, and PersonCell accept an azureId (preferred) or upn prop. Do not pass name strings or fetch people data manually.
Picker and viewer components work with `PersonInfo` objects — PersonPicker, PeoplePicker, and PeopleViewer accept a person (PersonInfo) or people (PersonInfo[]) prop. Do not pass raw azureId/upn strings to these components.
Picker/selector event pattern — person picker/selector components fire custom DOM events, not standard React callbacks. Always access the person via e.detail:
onPersonAdded={(e: PersonAddedEvent) => {
const person: PersonInfo = e.detail;
}}`person-click` DOM event — display components (PersonCard, PersonListItem, PersonCell) fire a person-click custom DOM event when the user clicks the person element. Access the clicked person via e.detail:
element.addEventListener('person-click', (e: PersonClickEvent) => {
const person: PersonInfo = e.detail;
});`PersonCell` in AG Grid — the cell renderer reads the cell value as an azureId string. If the azureId is nested, use valueGetter to extract it:
{ cellRenderer: PersonCell, valueGetter: ({ data }) => data?.owner?.azureId }Step 4: Check for common pitfalls
- Fetching person data manually: unnecessary — all components resolve from the Fusion People API by
azureId. Remove any manual fetch + state pattern. - Using `onPersonAdded` as a plain function callback: wrong — it is a DOM event handler. Access the person via
e.detail, not the argument directly. - Passing a name string as `azureId`: will silently fail. Ensure the value is a valid Azure AD object ID (UUID format).
- `PersonCell` receives an object instead of a string: use
valueGetterto extract theazureIdbefore passing to the renderer. - Using `PeopleViewer` without `PeoplePicker`:
PeopleVieweronly displays — it does not manage selection state. Pair withPeoplePickerwhen selection is also needed.
Step 5: Report
Produce:
- Component chosen with one-line rationale
- Usage snippet (copy-pasteable)
- Pitfalls found (if reviewing existing code)
- References:
references/using-fusion-react-components.md, Storybook docs
Styling Agent
Role
Use this helper agent to review or advise on styling decisions — EDS component selection, styled-components usage, design token application, and visual consistency with the Equinor Design System.
Inputs
file_paths: component files to reviewquestion: specific styling question, if anycomponent_name: EDS component being considered, if looking for guidance
MCP tooling
When the Fusion MCP server is available, prefer `mcp_fusion_search_eds` to look up EDS component documentation, props, usage examples, and accessibility guidance. This is more reliable than relying on memory alone.
Example queries:
mcp_fusion_search_eds→"Button props variants color disabled"mcp_fusion_search_eds→"Dialog modal usage examples"mcp_fusion_search_eds→"Typography variants heading body"mcp_fusion_search_eds→"color tokens CSS variables light dark"mcp_fusion_search_eds→"EdsDataGrid ag-grid wrapper"
Process
Step 1: Read the styling surface
1. Read the target component files. 2. Check which EDS components and tokens are imported. 3. Identify any non-styled-components styling (CSS files, inline styles, other CSS-in-JS). 4. Check if a Styled object pattern is used consistently.
Step 2: Evaluate EDS usage
Check:
- Component selection: Is there an EDS component that fits this use case? Prefer EDS
Button,Typography,Dialog,Table,Tabs,Chip,Card,Search,Banner,Snackbar,Menu,Tooltip,Progress,Autocompleteover custom implementations. - Icon usage: Uses
@equinor/eds-iconsdata objects with<Icon data={...} />, not raw SVGs or icon fonts. - Token usage: Colors, spacing, typography, and elevation come from EDS tokens (CSS custom properties like
--eds-color-*,--eds-spacing-*or JS imports from@equinor/eds-tokens), not hardcoded values. - Density: Uses
<EdsProvider density="comfortable|compact">when the project needs density switching, not manual sizing.
Step 3: Evaluate styled-components patterns
Check:
- `Styled` object pattern: Styled elements are grouped in a
const Styled = { ... }at the top of the file. - Extending EDS: Custom styling on EDS components uses
styled(EdsComponent), not class overrides or!important. - Responsive design: Media queries are inside styled-component template literals, not in separate CSS.
- No forbidden patterns: No CSS Modules, no global CSS imports for component styling, no Tailwind in the Fusion ecosystem.
Step 4: Check accessibility
- Interactive elements have accessible labels (
aria-label,aria-labelledby, or visible text). - Color is not the sole way to convey information.
- EDS components handle most accessibility automatically — verify custom elements meet the same standard.
aria-disabledis preferred overdisabledfor buttons that need tooltip support.
Step 5: Report findings
Produce a concise list:
- Correct: patterns following EDS and styled-components conventions
- Issues: problems with specific fix recommendations (e.g. "replace hardcoded
#007079withvar(--eds-color-bg-accent-fill-emphasis-default)") - Suggestions: EDS component alternatives, token replacements, accessibility improvements
Reference references/styling-with-eds.md and references/styled-components.md for the canonical patterns.
Charts Library Decision Matrix
Use this matrix when a developer asks about charting in a Fusion Framework app and the library choice is not already decided.
Decision table
| Factor | AG Charts | Chart.js |
|---|---|---|
| Fusion alignment | First-class via @equinor/fusion-framework-react-ag-charts | Third-party; no Fusion wrapper |
| Version management | Centrally managed across all Fusion apps | Managed per-app in package.json |
| AG Grid integration | Native via IntegratedChartsModule | Not supported |
| Community chart types | Bar, line, area, pie, donut, scatter, bubble | Bar, line, area, pie, doughnut, scatter, bubble, radar, polar |
| Enterprise chart types | Waterfall, heatmap, sunburst, treemap, stock, maps | Not available |
| TypeScript support | Full type definitions via Fusion wrapper | Full type definitions |
| Bundle size | Larger; offset by shared singleton in Fusion portal | Smaller; tree-shakeable per component |
| Interactivity | Built-in pan, zoom, crosshairs, tooltips | Basic tooltips; plugins needed for advanced interaction |
| License | Community: free; Enterprise: commercial license required | MIT |
Default recommendation
Recommend AG Charts for new Fusion Framework apps. The Fusion wrapper ensures version consistency across the portal.
When to recommend Chart.js instead
- Quick prototype where bundle size matters
- The user explicitly prefers Chart.js
- Radar or polar area charts are needed (AG Charts community does not include these)
- Simple one-off chart that does not justify the AG Charts dependency
When to recommend AG Grid integrated charts
- The page already has an AG Grid showing the same dataset
- Users should be able to create ad-hoc charts from grid data
- The chart and grid need to stay in sync
Sources
Follow-Up Questions
Clarifying questions to ask before implementing, organized by domain. Pick the relevant section based on what the user is building. Skip questions the user already answered.
Components (UI / Presentation)
- What data does this component display? Is the shape already defined in
src/types/? - Which EDS components are closest to the desired look? (e.g.
Card,Table,Dialog,Tabs) - Does this component need to handle loading/error states, or does a parent handle that?
- Should it support both compact and comfortable density, or only one?
- Is this a standalone component or a child of an existing page/layout?
- Are there interactive elements (buttons, inputs, toggles)? What actions do they trigger?
Hooks (State & Side Effects)
- What triggers this hook? (mount, context change, user action, interval)
- Does the hook wrap an API call, or purely manage local state?
- Should the result be cached/shared across components (React Query), or local to one component?
- Does the hook depend on the current Fusion context (
useCurrentContext)? - What should happen on error — retry, fallback value, or propagate to the caller?
Data Fetching / API Integration
- Is the API endpoint already registered in
app.config.tsorconfig.ts? - What is the base path and HTTP method? (e.g.
GET /api/items/:id) - Does the endpoint require authentication scopes? Which ones?
- What does the response shape look like? Is there a type/interface already?
- Should results be cached? What's a reasonable stale time?
- Does the data depend on the current Fusion context (project/facility)?
- Is this a read (query) or write (mutation) operation?
Routing
- How many pages/views does this feature need?
- What URL structure makes sense? (e.g.
/items,/items/:id,/items/:id/edit) - Does each page need its own data loader (
clientLoader)? - Is there a shared layout (nav bar, sidebar) across these pages?
- Should routes be documented in the app manifest (
app.manifest.ts)? - Are there any route parameters or search params to handle?
Context (Fusion Context Module)
- Which context type does the app operate on? (
ProjectMaster,Facility, custom?) - Does the app need to sync with the portal's context picker, or manage its own?
- Should data refetch automatically when the user switches context?
- Does the app need related contexts (e.g. tasks under a project)?
- Is there a custom context backend, or does it use the standard Fusion context API?
Styling
- Is there a design spec, mockup, or reference to follow?
- Which EDS components should be used as the base?
- Does the component need responsive behavior? At which breakpoints?
- Are there any custom colors or spacing beyond EDS tokens?
- Should the component adapt to density switching (
EdsProvider)?
AG Grid
- What data populates the grid? Is the type defined?
- Which columns are needed? Any custom formatters or cell renderers?
- Does the grid need sorting, filtering, or column reordering?
- Is row selection needed? Single or multi-select?
- Should the grid export data (Excel, clipboard)?
- Are Enterprise features required, or is Community sufficient?
Charts & Visualization
- What data should the chart display? Is the shape already defined?
- Which chart type fits the data? (bar, line, area, pie, scatter, or let the user choose?)
- Is the chart standalone, or should it be created from AG Grid data (integrated charts)?
- Does the chart need to update reactively when data changes?
- Are enterprise chart types needed (waterfall, heatmap, sunburst, treemap)?
- Is there a library preference (AG Charts vs Chart.js), or should we go with the default (AG Charts)?
- How many charts appear on the page? Is this a dashboard layout?
- Does the chart need interactive features (zoom, pan, crosshairs, drill-down)?
Module Configuration
- Which Fusion modules does this feature need? (HTTP, context, navigation, feature flags, settings)
- Are there existing modules configured in
config.tsthat this feature should reuse? - Does this feature introduce a new API backend that needs an HTTP client?
- Are there environment-specific differences (dev vs. prod endpoints, scopes)?
Dev Server / Mocking
- Is a real backend available, or do we need mock routes in
dev-server.config.ts? - If proxying, what is the upstream URL? Does it need authentication?
- Should the mock return static data or generated/dynamic data?
- Does the mock need to simulate error responses for testing?
New App Checklist
Step-by-step checklist for creating a new Fusion Framework React app from scratch. Use as a progress tracker — tick items off as you go.
1. Scaffold
- [ ] Create the app with
fusion-framework-cli app create <name>or manually scaffold the directory - [ ] Verify
package.jsonhas the correctname,scripts, and@equinor/fusion-framework-clidevDependency - [ ] Confirm
tsconfig.jsonuses strict mode and"moduleResolution": "bundler" - [ ] Confirm the entry point pattern in
src/index.ts(renderApp(App, configure))
2. Configuration files
- [ ]
app.manifest.ts— setappKeyanddisplayName - [ ]
app.config.ts— defineenvironmentandendpointsfor API backends - [ ]
src/config.ts— exportconfigure: AppModuleInitiator(can start empty) - [ ]
src/App.tsx— root component rendering the layout shell
3. Module setup (in config.ts, as needed)
- [ ] HTTP clients — registered via
app.config.tsendpoints (auto) orconfigureHttpClient(custom) - [ ] Context —
enableContext(configurator, (builder) => { builder.setContextType([...]) }) - [ ] Navigation —
enableNavigation(configurator, env.basename)if using routing - [ ] Feature flags —
configurator.enableFeatureFlag({ key, title })if toggling features - [ ] AG Grid —
enableAgGrid(configurator)if using data grids
4. Routing (if multi-page)
- [ ] Install
@equinor/fusion-framework-react-router - [ ] Create
src/routes.tsusing the DSL (layout,index,route,prefix) - [ ] Create
src/Router.tsxwith<Router routes={routes} /> - [ ] Add the Vite plugin
reactRouterPlugin()if using the route DSL - [ ] Wire router into
App.tsx - [ ] Update
app.manifest.tswithroutes: await toRouteSchema(routes)for portal discovery
5. Dev server
- [ ] Create
dev-server.config.tsif mocking APIs or proxying to a local backend - [ ] Verify the dev server starts without errors (e.g.
bun run dev/pnpm dev) - [ ] Confirm the app loads in the browser at the dev server URL
6. First feature
- [ ] Define types in
src/types/ - [ ] Create API layer in
src/api/(hooks wrappinguseHttpClientor React Query) - [ ] Create components in
src/components/ - [ ] Create hooks in
src/hooks/for state and side-effect logic - [ ] Wire everything together in a page or the root
App.tsx
7. Styling
- [ ] EDS components (
@equinor/eds-core-react) used as the base for UI elements - [ ]
styled-componentsused for custom styling with theStyledobject pattern - [ ] EDS design tokens used for colors, spacing, and typography (no hardcoded values)
- [ ] Icons imported from
@equinor/eds-iconsas data objects
8. Code quality
- [ ] TSDoc on every exported function, component, hook, and type
- [ ] Inline why comments on iterators, decision gates, and complex logic
- [ ] No
anytypes — TypeScript strict mode enforced - [ ] Explicit return types on exported functions
- [ ] Conventional commits used for all changes
9. Validation
- [ ] Typecheck — zero errors (e.g.
bun run typecheck/pnpm typecheck) - [ ] Lint — zero violations (e.g.
bun run lint/biome check src/) - [ ] Production build succeeds (e.g.
bun run build/pnpm build) - [ ] No new dependencies added without justification
- [ ] No secrets or credentials in source files
10. Documentation
- [ ]
README.mdupdated with app description, setup instructions, and available scripts - [ ] ADR created in
docs/adr/for significant architectural decisions - [ ]
contribute/directory reviewed for project-specific code standards
Post-Generation Review Checklist
Run through this checklist after generating or modifying code. Skip items that don't apply to the change.
TypeScript
- [ ] No
anytypes — use specific types, generics, orunknownwith narrowing - [ ] No type assertions (
as) where proper typing is possible - [ ] No non-null assertions (
!) without clear justification - [ ] Explicit return types on all exported functions
- [ ] Discriminated unions used for narrowing instead of type casting
- [ ] Generic constraints are specific, not
T extends any
TSDoc
- [ ] Every exported function, component, hook, class, and type has TSDoc
- [ ] Summary explains intent and why, not restating the name
- [ ]
@paramfor every parameter (without restating the type) - [ ]
@returnsfor every non-void function - [ ]
@templatefor every generic type parameter - [ ]
@throwsfor meaningful error paths - [ ]
@examplefor user-facing and non-trivial public APIs
Naming
- [ ] Components: PascalCase file and export name
- [ ] Hooks:
useprefix, camelCase file name - [ ] Types/interfaces: PascalCase, no
Iprefix - [ ] Constants: SCREAMING_SNAKE_CASE
- [ ] Variables and functions: camelCase
- [ ] Names are descriptive and self-documenting
Code Quality
- [ ] Each function/component has a single responsibility
- [ ] Immutable patterns used (
map,filter,reduce) over mutable accumulators - [ ] No dead code: unused imports, unreachable branches, commented-out blocks
- [ ] Inline why comments on iterators, decision gates, complex logic
- [ ] No comments that restate syntax or obvious control flow
- [ ] Non-trivial inline callbacks extracted into named helpers with TSDoc
- [ ] Error handling uses specific error types with context, not bare
Error
EDS & Styling
- [ ] EDS components used before building custom UI elements
- [ ]
styled-componentsused for custom styling (not CSS Modules, Tailwind, global CSS) - [ ]
Styledobject pattern used for co-located styled components - [ ] EDS design tokens used for colors, spacing, typography (no hardcoded hex/px values)
- [ ] Icons from
@equinor/eds-iconswith<Icon data={...} /> - [ ] Inline
styleprops only for one-off tweaks, not reusable patterns
Accessibility
- [ ] Interactive elements have accessible labels (
aria-label,aria-labelledby, or visible text) - [ ]
aria-disabledused instead ofdisabledon buttons that need tooltip support - [ ]
titleprovided on<Icon>for screen readers - [ ] Color is not the sole way to convey information
Fusion Framework
- [ ] HTTP clients accessed via
useHttpClient(name), not rawfetch() - [ ] No duplicate client registrations (same client in both
app.config.tsandconfig.ts) - [ ] Context read from
useCurrentContext(), not duplicated into local state - [ ] No hardcoded URLs — endpoints come from
app.config.tsor environment variables - [ ] No manual token management — MSAL module handles auth
- [ ] Per-user preferences use app settings, not ad hoc runtime config or local storage by default
- [ ] Bookmark payloads are serializable and limited to shareable view state
- [ ] Analytics events use
useTrackFeature()or the project's approved wrapper - [ ] Module hooks (
useHttpClient,useCurrentContext, etc.) called inside React components/hooks only
Data Fetching
- [ ] React Query used for server state, React state for client UI state
- [ ] Query keys derived from API path + parameters
- [ ] Queries disabled (
enabled: false) when required parameters are missing - [ ] Mutations invalidate related queries on success
- [ ] Loading and error states handled in the UI
Charts & Visualization
- [ ] AG Charts imports come from
@equinor/fusion-framework-react-ag-charts, not directly fromag-charts-* - [ ]
ModuleRegistry.registerModules([AllCommunityModule])called once at startup before any chart renders - [ ] Chart data has a typed interface, not inline untyped objects
- [ ] Chart options use
AgChartOptionstype (AG Charts) or library option types (Chart.js) - [ ] Data fetching, transformation, and chart rendering are in separate layers
- [ ] Charts are in a responsive container with explicit sizing
- [ ] Enterprise imports only when enterprise features are needed and license is available
- [ ] AG Grid integrated charts use
IntegratedChartsModule.with(AgChartsEnterpriseModule)inenableAgGrid
Dependencies
- [ ] No new dependencies added without explicit justification
- [ ] No secrets or credentials in source files
- [ ] No direct DOM manipulation — React patterns used
Validation
- [ ] Typecheck passes with zero errors (e.g.
bun run typecheck/pnpm typecheck) - [ ] Lint passes with zero violations (e.g.
bun run lint/biome check src/) - [ ] Conventional commit message prepared (
feat:,fix:,refactor:, etc.)
Changelog
0.3.0 - 2026-05-07
minor
- Add
agents/design.mdhelper agent covering Fusion Portal shell composition, layout zone nesting, side panel usage (SideSheet), empty/loading state patterns, and structural anti-patterns - Update
SKILL.mdStep 4 to referencedesign.mdfor page/view structure review andstyling.mdfor component-level checks - Update helper agents section to include
design.mdwith clear scope boundary vsstyling.md
Agent references equinor-design-system system skill for authoritative token and layout zone ground truth, and delegates component-level EDS checks to agents/styling.md.
resolves equinor/fusion-core-tasks#860
patch
- Drop articles, filler, hedging from SKILL.md activation body
- Compress all using-.md, configure-.md, styled-components, styling-with-eds, project-structure references
0.2.0 - 2026-05-05
minor
- Add
references/using-custom-modules.mdcovering the IModule contract, wiring intoconfig.ts, accessing viauseAppModule, module lifecycle, file structure, and common pitfalls (naming conflicts, outside-React access, un-awaited config) - Update
SKILL.mdStep 6 module table with ausing-custom-modules.mdentry - Add custom-module-related activation triggers
resolves equinor/fusion-core-tasks#753
- Add
references/using-people-service.mdcovering the preferredazureId-only integration pattern, picker components,PersonCellfor AG Grid, and guidance on what NOT to do (manual API calls, caching PersonInfo) - Update
SKILL.mdStep 6 module table with ausing-people-service.mdentry - Add people-related activation triggers to the skill description
resolves equinor/fusion-core-tasks#748
- Expand
references/using-fusion-react-components.mdwithPersonCard,PersonListItem, andPersonCell(AG Grid) usage examples - Add a decision guide table covering all person components and their intended use cases
- Add new
agents/person-components.mdhelper agent covering component selection, the custom DOM event pattern,PersonCellvalueGetter setup, and common pitfalls - Update
SKILL.mdhelper agents section and Step 6 module table to reference the new agent
resolves equinor/fusion-core-tasks#858
- add companion-skill metadata for
fusion-research-framework - delegate framework API and package research before choosing app implementation patterns
- align the framework helper agent with the shared source-backed research workflow
0.1.0 - 2026-03-18
minor
Guides feature development in Fusion Framework React apps — scaffolding components, hooks, services, and types that follow EDS conventions and Fusion Framework patterns. Includes helper agents for framework review, styling review, and code-quality review, plus reference docs and asset checklists.
resolves equinor/fusion-core-tasks#799
Configure Dev Server (Mocking & Proxying)
How to mock API responses and proxy to local backends during development using dev-server.config.ts.
All dev-time API configuration lives in dev-server.config.ts at the project root — not in app.config.ts. The Fusion CLI dev server loads this file automatically and merges it with its defaults.
Mock API routes
Define middleware routes that return mock data without a real backend:
// dev-server.config.ts
import { defineDevServerConfig } from '@equinor/fusion-framework-cli/dev-server';
export default defineDevServerConfig(() => ({
api: {
routes: [
{
match: '/api/items',
middleware: (_req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify([
{ id: '1', name: 'Item A' },
{ id: '2', name: 'Item B' },
]));
},
},
{
match: '/api/items/:id',
middleware: (req, res) => {
const id = req.params?.id;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ id, name: `Item ${id}` }));
},
},
],
},
}));App code uses fetch('/api/items') or useHttpClient(name) — dev server intercepts matching routes before network calls.
Proxy to a local backend (simple)
For a local API (e.g. Docker Compose), add a proxy route:
// dev-server.config.ts
import { defineDevServerConfig } from '@equinor/fusion-framework-cli/dev-server';
const MY_API_TARGET = process.env.MY_API_URL ?? 'http://localhost:3000';
export default defineDevServerConfig(() => ({
api: {
routes: [
{
match: '/my-api/*path',
proxy: {
target: MY_API_TARGET,
rewrite: (path) => path.replace(/^\/my-api/, ''),
changeOrigin: true,
secure: false,
},
},
],
},
}));What happens: 1. Request to /my-api/v1/example matches /my-api/*path 2. Proxy strips the /my-api prefix 3. Forwards to http://localhost:3000/v1/example
Optional: named HTTP client
No app config is required — fetch('/my-api/...') works directly. If you want a named client:
// src/config.ts
export const configure: AppModuleInitiator = async (configurator) => {
configurator.useHttpClient('my-api', {
baseUri: '/my-api',
});
};Then use useHttpClient('my-api') instead of raw fetch().
Proxy with service discovery (authenticated)
When the backend should appear in Fusion service discovery (so the HTTP module acquires tokens automatically), use processServices:
// dev-server.config.ts
import { defineDevServerConfig, processServices } from '@equinor/fusion-framework-cli/dev-server';
const MY_API_KEY = 'my-api';
const MY_API_TARGET = process.env.MY_API_URL ?? 'https://api.example.com';
const MY_API_SCOPES = ['api://my-api/.default'];
const MY_API_MATCH = `/${MY_API_KEY}/*path`;
export default defineDevServerConfig(() => ({
api: {
processServices: (data, args) => {
// Keep all existing service discovery proxies
const existing = processServices(data, args);
// Build a URI that points back to the local dev server
const referer = args.request.headers.referer ?? 'http://localhost';
const localUri = new URL(`${args.route}/${MY_API_KEY}`, referer).href;
return {
data: [
...existing.data.filter((svc) => svc.key !== MY_API_KEY),
{
key: MY_API_KEY,
name: 'My API (local dev proxy)',
uri: localUri,
scopes: MY_API_SCOPES,
defaultScopes: MY_API_SCOPES,
},
],
routes: [
...(existing.routes ?? []),
{
match: MY_API_MATCH,
proxy: {
target: MY_API_TARGET,
rewrite: (path) => path.replace(`/${MY_API_KEY}`, ''),
changeOrigin: true,
secure: false,
},
},
],
};
},
},
}));App setup
Register the discovered service once:
// src/config.ts
export const configure: AppModuleInitiator = async (configurator) => {
await configurator.useFrameworkServiceClient('my-api');
};Then in components:
import { useHttpClient } from '@equinor/fusion-framework-react-app/http';
const client = useHttpClient('my-api');
// The HTTP module acquires a bearer token for the configured scopes
// and the dev-server proxy forwards the authenticated request upstreamRequest flow
App startup
→ configurator.useFrameworkServiceClient('my-api')
→ service discovery resolves my-api (injected by dev-server)
Component code
→ useHttpClient('my-api')
→ HTTP module acquires token for api://my-api/.default
→ GET {local proxy uri}/v1/example with Authorization header
→ dev-server proxy matches /my-api/*path
→ proxy strips prefix, forwards to https://api.example.com/v1/example
→ upstream API receives the bearer tokenChoosing a strategy
| Strategy | Auth? | Service discovery? | Use when |
|---|---|---|---|
| Mock routes | No | No | No backend yet, need static/fake data |
| Simple proxy | Manual | No | Local backend (Docker), auth optional |
| Service discovery proxy | Automatic | Yes | Backend needs bearer tokens via MSAL |
Configuration format
dev-server.config.ts supports two export styles:
// Object export (simple)
export default {
api: { routes: [...] },
};
// Function export (conditional logic, access to base config)
import { defineDevServerConfig } from '@equinor/fusion-framework-cli/dev-server';
export default defineDevServerConfig(({ base }) => ({
...base,
api: { routes: [...] },
}));Routes with identical match paths are replaced (yours wins). Other arrays are deduplicated automatically.
Configure Services (HTTP Clients)
How to register and consume HTTP clients in a Fusion Framework app.
Registration strategies
There are three ways to register a named HTTP client. Choose the simplest one that fits:
| Strategy | Where | When to use |
|---|---|---|
app.config.ts endpoints | app.config.ts | Default — auto-registered, no code in config.ts |
configureHttpClient | config.ts | Custom transport behavior (headers, guards, custom client class) |
| Service discovery | config.ts | Framework-managed Fusion platform services |
Via app.config.ts (auto-registration)
Endpoints defined here are automatically registered as named HTTP clients — no extra code in config.ts:
import { defineAppConfig } from '@equinor/fusion-framework-cli/app';
export default defineAppConfig((_env, _args) => ({
environment: {},
endpoints: {
myApi: {
url: 'https://api.example.com',
scopes: ['api://my-api/.default'],
},
},
}));After initialization, use the client directly:
const client = useHttpClient('myApi');
const data = await client.json('/items');Via configureHttpClient in config.ts
Use when the endpoint needs custom transport behavior or is not in app.config.ts:
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
export const configure: AppModuleInitiator = (configurator, env) => {
configurator.configureHttpClient('my-api', {
baseUri: 'https://api.example.com',
defaultScopes: ['api://my-api/.default'],
onCreate: (client) => {
client.requestHandler.setHeader('X-App-Name', 'my-app');
},
});
};Multiple clients in one step
Access configurator.http directly when registering several backends in one block:
configurator.http.configureClient('catalog', {
baseUri: '/api/catalog',
});
configurator.http.configureClient('search', {
baseUri: '/api/search',
});Custom client class
Extend HttpClient for domain-specific methods:
import { HttpClient } from '@equinor/fusion-framework-module-http/client';
class ApiClient extends HttpClient {
getHealth(): Promise<{ status: string }> {
return this.json('/health');
}
}
configurator.configureHttpClient('api', {
baseUri: '/api',
ctor: ApiClient,
});
// Usage:
const client = framework.modules.http.createCustomClient<ApiClient>('api');
const health = await client.getHealth();Via service discovery
For Fusion platform services:
configurator.useFrameworkServiceClient('people');Consuming HTTP clients
In React components and hooks, prefer @equinor/fusion-framework-react-app/* hooks over direct module access. Reserve useAppModule and framework.modules.* for non-React contexts (route loaders, scripts).
Preferred: useHttpClient hook
import { useHttpClient } from '@equinor/fusion-framework-react-app/http';
const MyComponent = () => {
const client = useHttpClient('my-api');
// client.json('/endpoint') — returns parsed JSON (Promise)
// client.fetchAsync('/endpoint') — returns raw Response (Promise)
// client.fetch$('/endpoint') — returns an Observable
};Fallback: useAppModule
Use only when a dedicated react-app hook is not available:
import { useAppModule } from '@equinor/fusion-framework-react-app';
const http = useAppModule('http');
const client = http.createClient('my-api');Ad-hoc clients
For one-off calls without a named configuration:
// In config.ts, inside your Fusion configuration callback where `framework` is provided:
const inlineClient = framework.modules.http.createClient({
baseUri: '/api/search',
});
// Or with an absolute URL:
const urlClient = framework.modules.http.createClient('https://api.example.com');Create a Fusion App
Bootstrapping and configuring Fusion Framework apps at entry-point level.
File structure
Every Fusion app has these top-level configuration files:
src/index.ts → renderApp() — mounts the app into the Fusion Portal
src/config.ts → AppModuleInitiator — configures Fusion modules before render
src/App.tsx → Root React component
app.config.ts → Runtime config (endpoints, environment)
app.manifest.ts → Portal metadata (appKey, displayName)Entry point (src/index.ts)
Modern pattern:
import { renderApp } from '@equinor/fusion-framework-react-app';
import { App } from './App';
import { configure } from './config';
export const render = renderApp(App, configure);
export default render;Older codebases use makeComponent + createRoot. Check project's index.ts and follow its convention.
Module configuration (src/config.ts)
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
export const configure: AppModuleInitiator = (configurator, { env }) => {
// Module configuration is added here as features are built
};
export default configure;See configure-services.md for HTTP client setup and using-framework-modules.md for other modules.
App manifest (app.manifest.ts)
Metadata for Fusion Portal:
import { defineAppManifest } from '@equinor/fusion-framework-cli/app';
export default defineAppManifest(async (_env, { base }) => ({
...base,
appKey: 'my-app',
displayName: 'My Application',
}));Runtime config (app.config.ts)
Environment variables and endpoints, resolved at dev-server startup or build time:
import { defineAppConfig } from '@equinor/fusion-framework-cli/app';
export default defineAppConfig((_env, _args) => ({
environment: {},
endpoints: {},
}));See configure-services.md for adding endpoints.
CLI commands
The Fusion Framework CLI (@equinor/fusion-framework-cli) handles development and builds:
| Command | Purpose |
|---|---|
fusion-framework-cli app dev | Start dev server with HMR |
fusion-framework-cli app build | Production build |
fusion-framework-cli app pack | Bundle into a zip archive |
fusion-framework-cli app publish | Upload to Fusion app service |
fusion-framework-cli app create <name> | Scaffold a new app from template |
Projects typically alias these in package.json scripts (e.g. dev, build). Check the project's package.json for the exact commands available.
Project Structure
Directory layout, layer responsibilities, file placement, tooling, and commit conventions for Fusion Framework apps.
Directory structure
Typical Fusion app layout:
src/
├── App.tsx # Root component — layout shell, routing
├── config.ts # Fusion module configuration
├── index.ts # Entry point (do not modify)
├── components/ # React components (presentation layer)
├── hooks/ # Custom React hooks
├── api/ # API clients, data transforms, business logic
└── types/ # TypeScript interfaces, type aliases, enumsProjects may add directories (e.g. pages/, utils/, context/). Check existing structure first.
Layer responsibilities
| Layer | Purpose | Example files |
|---|---|---|
components | Presentation: render UI, handle user events | DataGrid.tsx, Header.tsx |
hooks | State & side effects: encapsulate logic | useItems.ts, useFilter.ts |
api | API clients, data transforms, business logic | itemApi.ts |
types | Type definitions: shared interfaces/enums | item.ts, api.ts |
File placement rules
- One primary export per file (component, hook, service, or type module).
- Co-locate related files: a page component and its dedicated hook can share a directory.
- Barrel exports (
index.ts) are optional — use when a directory has 3+ exports.
Tooling
Check the project's package.json scripts and installed devDependencies for exact commands. Common Fusion app tooling:
| Tool | Typical command | Purpose |
|---|---|---|
| TypeScript | typecheck / tsc --noEmit | Type checking (strict mode) |
| Biome | biome check src/ | Lint + format validation |
| ESLint | eslint src/ | Lint (if project uses it) |
| Dev server | dev | Start Fusion CLI dev server |
| Build | build | Production build |
Run commands via the project's package manager (bun run, pnpm, npm run).
Commit conventions
Use conventional commits for all changes:
| Prefix | When to use |
|---|---|
feat: | New feature or capability |
fix: | Bug fix |
refactor: | Code restructuring without behavior change |
docs: | Documentation only |
style: | Formatting, whitespace, missing semicolons |
test: | Adding or fixing tests |
chore: | Build, tooling, dependency updates |
Architecture Decision Records
Check for ADRs in docs/adr/. Follow them. Propose new ADRs for architectural decisions not already covered.
styled-components Patterns
Using styled-components for custom styling in Fusion Framework apps.
The Styled object pattern
Co-locate styled components in a Styled namespace object at top of file:
import { styled } from 'styled-components';
import { Typography } from '@equinor/eds-core-react';
const Styled = {
Root: styled.section`
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
`,
Title: styled(Typography)`
color: var(--eds-color-text-neutral-strong);
`,
};Then use in the component:
const MyComponent = () => (
<Styled.Root>
<Styled.Title variant="h2">Hello</Styled.Title>
</Styled.Root>
);Why this pattern:
- Groups all styled elements visually at the top
- Clear namespace avoids collision with imported components
- Easy to scan which elements have custom styling
Using EDS tokens in styled-components
CSS custom properties (preferred)
const Styled = {
Card: styled.div`
background: var(--eds-color-bg-neutral-surface);
border-radius: var(--eds-shape-corners-border-radius);
padding: var(--eds-spacing-comfortable-medium);
`,
};JS token imports
import { tokens } from '@equinor/eds-tokens';
const Styled = {
Card: styled.div`
background: ${tokens.colors.ui.background__default.rgba};
border-radius: ${tokens.shape.corners.borderRadius};
padding: ${tokens.spacings.comfortable.medium};
box-shadow: ${tokens.elevation.raised};
`,
};Extending EDS components
Wrap EDS components with styled() for layout or theme tweaks:
import { Button, Dialog } from '@equinor/eds-core-react';
const Styled = {
WideButton: styled(Button)`
min-width: 200px;
`,
FullWidthDialog: styled(Dialog)`
width: 90vw;
max-width: 800px;
`,
};Responsive design
Use standard CSS media queries inside styled-components:
const Styled = {
Grid: styled.div`
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
@media (min-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 1200px) {
grid-template-columns: repeat(3, 1fr);
}
`,
};What NOT to do
- No CSS Modules (
.module.cssfiles) unless the project explicitly uses them - No global CSS files (
.cssimports) for component styling - No Tailwind or utility-class libraries unless the project adopts them
- No Emotion, Stitches, or other CSS-in-JS alternatives in Fusion apps
- No inline styles for reusable patterns — extract to
Styledobject instead - No hardcoded colors or spacing — use EDS tokens (see
styling-with-eds.md)
Styling with EDS
Using EDS components and design tokens in Fusion Framework apps.
Packages
| Package | Purpose |
|---|---|
@equinor/eds-core-react | Core UI components (Button, Dialog, Typography, etc.) |
@equinor/eds-icons | SVG icon data objects |
@equinor/eds-tokens | Design tokens — colors, spacing, typography, elevation |
@equinor/eds-data-grid-react | EDS-themed AG Grid wrapper (see using-ag-grid.md) |
@equinor/fusion-react-* | Fusion-specific React components not in EDS (see using-fusion-react-components.md) |
Component catalog
Frequently used components from @equinor/eds-core-react:
| Component | Use for |
|---|---|
Typography | All text (headings, body, labels) |
Button | Actions and CTAs |
Dialog | Modal dialogs |
Table | Simple non-interactive tables |
Tabs | Tab navigation |
Chip | Tags, filters, status badges |
Card | Content containers |
Search | Search input fields |
Autocomplete | Filtered dropdown selection |
Icon | Icons from @equinor/eds-icons |
Progress | Loading indicators (.Dots, .Circular) |
Banner | Informational banners |
Snackbar | Toast notifications |
Menu | Dropdown menus |
Tooltip | Hover information |
Checkbox | Toggle inputs |
Radio | Single selection from a group |
TextField | Text input fields |
Switch | On/off toggle |
EdsProvider | Density switching (compact/comfortable) |
Check @equinor/eds-core-react before building custom UI elements.
Design tokens
Use EDS tokens for all visual values — never hardcode colors, spacing, or typography.
CSS custom properties (preferred)
When EDS theme is active (Fusion Portal provides this):
background: var(--eds-color-bg-neutral-surface);
color: var(--eds-color-text-neutral-strong);
border: 1px solid var(--eds-color-border-neutral-subtle);
padding: var(--eds-spacing-comfortable-medium);Common token categories:
--eds-color-bg-*— background colors (neutral, accent, success, info, warning, danger)--eds-color-text-*— text colors--eds-color-border-*— border colors--eds-spacing-*— spacing values
JS token imports
When CSS custom properties are not available:
import { tokens } from '@equinor/eds-tokens';
tokens.colors.ui.background__default.rgba // background color
tokens.shape.corners.borderRadius // border radius
tokens.spacings.comfortable.medium // spacing
tokens.elevation.raised // box shadowIcons
Use @equinor/eds-icons for icon data and the Icon component to render:
import { Icon } from '@equinor/eds-core-react';
import { edit, save, delete_to_trash, search, close } from '@equinor/eds-icons';
<Icon data={edit} title="Edit" />
<Icon data={save} title="Save" />Import names use snake_case. Browse available icons at eds.equinor.com.
Density
Toggle density:
import { EdsProvider } from '@equinor/eds-core-react';
<EdsProvider density="compact">
{/* All child EDS components render in compact mode */}
</EdsProvider>Accessibility
- EDS components handle most accessibility automatically.
- Use
aria-disabledinstead ofdisabledon buttons that need tooltip support. - Always provide
titleon<Icon>for screen readers, oraria-labelon icon buttons. - Color must not be the sole way to convey information.
Using AG Charts
Rendering standalone charts with @equinor/fusion-framework-react-ag-charts.
Quick start
Install
# use the project's package manager (bun / pnpm / npm)
bun add @equinor/fusion-framework-react-ag-chartsRegister modules
Register chart modules once before rendering any chart:
import {
AllCommunityModule,
ModuleRegistry,
} from '@equinor/fusion-framework-react-ag-charts/community';
ModuleRegistry.registerModules([AllCommunityModule]);Place in app entry point or top-level initializer. Charts fail silently without module registration.
Render a chart
import { useState } from 'react';
import { AgCharts } from '@equinor/fusion-framework-react-ag-charts';
import type { AgChartOptions } from '@equinor/fusion-framework-react-ag-charts/community';
const SalesChart = () => {
const [chartOptions] = useState<AgChartOptions>({
data: [
{ month: 'Jan', avgTemp: 2.3, iceCreamSales: 162000 },
{ month: 'Mar', avgTemp: 6.3, iceCreamSales: 302000 },
{ month: 'May', avgTemp: 16.2, iceCreamSales: 800000 },
{ month: 'Jul', avgTemp: 22.8, iceCreamSales: 1254000 },
{ month: 'Sep', avgTemp: 14.5, iceCreamSales: 950000 },
{ month: 'Nov', avgTemp: 8.9, iceCreamSales: 200000 },
],
series: [{ type: 'bar', xKey: 'month', yKey: 'iceCreamSales' }],
});
return <AgCharts options={chartOptions} />;
};Packages and entry points
| Sub-path | What it provides | Upstream package |
|---|---|---|
@equinor/fusion-framework-react-ag-charts | AgCharts React component and React-level utilities | ag-charts-react |
@equinor/fusion-framework-react-ag-charts/community | AllCommunityModule, ModuleRegistry, AgChartOptions, AgChartTheme | ag-charts-community |
@equinor/fusion-framework-react-ag-charts/enterprise | AgChartsEnterpriseModule and enterprise-only chart types | ag-charts-enterprise |
Always import from the Fusion wrapper, not from ag-charts-* directly — wrapper ensures a single centrally managed version.
Multi-series chart
const chartOptions: AgChartOptions = {
data: [
{ quarter: 'Q1', revenue: 45000, expenses: 30000 },
{ quarter: 'Q2', revenue: 52000, expenses: 35000 },
{ quarter: 'Q3', revenue: 61000, expenses: 38000 },
{ quarter: 'Q4', revenue: 58000, expenses: 36000 },
],
series: [
{ type: 'bar', xKey: 'quarter', yKey: 'revenue', yName: 'Revenue' },
{ type: 'bar', xKey: 'quarter', yKey: 'expenses', yName: 'Expenses' },
],
};Common chart types
Line chart
series: [
{ type: 'line', xKey: 'month', yKey: 'temperature', yName: 'Avg Temperature' },
]Area chart
series: [
{ type: 'area', xKey: 'month', yKey: 'revenue', yName: 'Revenue' },
]Pie chart
series: [
{ type: 'pie', angleKey: 'share', legendItemKey: 'segment' },
]Enterprise chart types
Requires a valid AG Charts license. Import from /enterprise:
import { AgChartsEnterpriseModule } from '@equinor/fusion-framework-react-ag-charts/enterprise';Enterprise-only types include: waterfall, heatmap, sunburst, treemap, stock charts, and maps.
Reactive chart updates
Use useState for chart options that change with user interaction or data updates:
const [chartOptions, setChartOptions] = useState<AgChartOptions>({
data: initialData,
series: [{ type: 'bar', xKey: 'category', yKey: 'value' }],
});
// Update data reactively
const handleDataUpdate = (newData: ChartDataItem[]) => {
setChartOptions((prev) => ({ ...prev, data: newData }));
};Data fetching pattern
Separate data fetching from chart rendering:
import { useHttpClient } from '@equinor/fusion-framework-react-app/http';
import { useQuery } from '@tanstack/react-query';
interface MetricItem {
period: string;
value: number;
}
const useMetrics = () => {
const httpClient = useHttpClient('my-api');
return useQuery({
queryKey: ['metrics'],
queryFn: () => httpClient.json<MetricItem[]>('/api/metrics'),
});
};
const MetricsChart = () => {
const { data, isLoading } = useMetrics();
const [chartOptions] = useState<AgChartOptions>({
data: data ?? [],
series: [{ type: 'bar', xKey: 'period', yKey: 'value' }],
});
if (isLoading) return <Progress.Linear />;
return <AgCharts options={chartOptions} />;
};Responsive sizing
AG Charts sizes to its container. Wrap in a sized element:
<div style={{ width: '100%', height: 400 }}>
<AgCharts options={chartOptions} />
</div>Or with styled-components:
const Styled = {
ChartContainer: styled.div`
width: 100%;
height: 400px;
`,
};
<Styled.ChartContainer>
<AgCharts options={chartOptions} />
</Styled.ChartContainer>Key concepts
- Module registration — AG Charts uses a modular architecture. Register modules via
ModuleRegistry.registerModules()before any chart renders.AllCommunityModuleis the easiest starting point. - Chart options — Charts are configured declaratively through an
AgChartOptionsobject describing data, series types, axes, legends, and themes. - Thin wrapper —
@equinor/fusion-framework-react-ag-chartsre-exports upstream AG Charts packages so Fusion apps share a single centrally managed version.
Sources
Using AG Grid Integrated Charts
How to enable chart creation from AG Grid data in a Fusion Framework app, using the AG Grid IntegratedChartsModule with AG Charts enterprise.
Prerequisites
@equinor/fusion-framework-react-ag-gridinstalled@equinor/fusion-framework-react-ag-chartsinstalled- A valid AG Charts Enterprise license (integrated charts require enterprise)
Enable integrated charts
Add IntegratedChartsModule.with(AgChartsEnterpriseModule) to the AG Grid module list in config.ts:
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
import { enableAgGrid } from '@equinor/fusion-framework-react-ag-grid';
import {
AllCommunityModule,
ClientSideRowModelModule,
} from '@equinor/fusion-framework-react-ag-grid/community';
import {
IntegratedChartsModule,
ColumnsToolPanelModule,
FiltersToolPanelModule,
MenuModule,
} from '@equinor/fusion-framework-react-ag-grid/enterprise';
import { AgChartsEnterpriseModule } from '@equinor/fusion-framework-react-ag-charts/enterprise';
export const configure: AppModuleInitiator = (configurator) => {
enableAgGrid(configurator, (builder) => {
builder.setModules([
AllCommunityModule,
ClientSideRowModelModule,
ColumnsToolPanelModule,
FiltersToolPanelModule,
MenuModule,
IntegratedChartsModule.with(AgChartsEnterpriseModule),
]);
});
};How users create charts from the grid
Once integrated charts are enabled, users can:
1. Select one or more columns in the grid. 2. Right-click on the selection. 3. Choose "Chart Range" from the context menu. 4. Select a chart type (bar, line, pie, area, etc.). 5. Customize using the chart toolbar.
No additional chart component code is needed — AG Grid renders the chart in a popup or docked panel.
Enable chart menu item
Ensure enableCharts: true is set on the grid (this is the default when IntegratedChartsModule is registered):
<AgGridReact
theme={theme}
rowData={rows}
columnDefs={columns}
enableCharts={true}
enableRangeSelection={true}
/>enableRangeSelection allows users to select cell ranges, which is the typical trigger for chart creation.
Programmatic chart creation
Create charts from code using the grid API:
const gridRef = useRef<AgGridReact>(null);
const createChart = () => {
gridRef.current?.api.createRangeChart({
chartType: 'groupedBar',
cellRange: {
columns: ['category', 'value'],
},
});
};When to use integrated charts vs standalone AG Charts
| Scenario | Approach |
|---|---|
| Users explore grid data visually on demand | Integrated charts |
| Fixed chart layout on a dashboard page | Standalone AG Charts |
| Chart data comes from a different source than the grid | Standalone AG Charts |
| Both grid and chart show the same dataset | Integrated charts |
Sources
Using AG Grid
Displaying tabular data with AG Grid in Fusion Framework apps using @equinor/fusion-framework-react-ag-grid.
Quick start
Install
# use the project's package manager (bun / pnpm / npm)
bun add @equinor/fusion-framework-react-ag-gridEnable the module
Register in src/config.ts:
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
import { enableAgGrid } from '@equinor/fusion-framework-react-ag-grid';
export const configure: AppModuleInitiator = (configurator) => {
enableAgGrid(configurator);
};Render a grid
import { AgGridReact } from '@equinor/fusion-framework-react-ag-grid';
import { useTheme } from '@equinor/fusion-framework-react-ag-grid';
import type { ColDef } from '@equinor/fusion-framework-react-ag-grid/community';
interface Item {
id: string;
name: string;
status: string;
}
const columnDefs: ColDef<Item>[] = [
{ field: 'name', headerName: 'Name', flex: 2 },
{ field: 'status', headerName: 'Status', width: 120 },
];
/**
* Renders a data grid with Fusion/EDS theming.
*
* @param props.items - The list of items to display.
* @returns A themed AG Grid component.
*/
const ItemGrid = ({ items }: { items: Item[] }) => {
const theme = useTheme();
return (
<AgGridReact
theme={theme}
rowData={items}
columnDefs={columnDefs}
/>
);
};Registering feature modules
Since AG Grid 33, explicitly register feature modules for tree-shaking. Use builder.setModules() inside the enableAgGrid callback:
import { enableAgGrid } from '@equinor/fusion-framework-react-ag-grid';
import {
ClientSideRowModelModule,
ValidationModule,
} from '@equinor/fusion-framework-react-ag-grid/community';
export const configure: AppModuleInitiator = (configurator) => {
enableAgGrid(configurator, (builder) => {
builder.setModules([
ClientSideRowModelModule,
ValidationModule,
]);
});
};With Enterprise license:
import {
ClipboardModule,
ColumnsToolPanelModule,
ExcelExportModule,
FiltersToolPanelModule,
MenuModule,
} from '@equinor/fusion-framework-react-ag-grid/enterprise';
builder.setModules([
ClientSideRowModelModule,
ClipboardModule,
ColumnsToolPanelModule,
ExcelExportModule,
FiltersToolPanelModule,
MenuModule,
]);Only import what you need — unregistered modules are tree-shaken out.
Theming
The module provides fusionTheme by default — Equinor-branded theme based on AG Grid Alpine with EDS accent colors.
Using the default theme
import { useTheme } from '@equinor/fusion-framework-react-ag-grid';
const MyGrid = ({ rows }) => {
const theme = useTheme();
return <AgGridReact theme={theme} rowData={rows} columnDefs={columns} />;
};Customizing the theme
enableAgGrid(configurator, (builder) => {
builder.setTheme((theme) => {
return theme.withParams({
backgroundColor: '#1f2836',
browserColorScheme: 'dark',
foregroundColor: '#FFF',
headerFontSize: 14,
});
});
});Per-instance customization
import { useTheme } from '@equinor/fusion-framework-react-ag-grid';
const MyGrid = () => {
const baseTheme = useTheme();
const theme = useMemo(
() => baseTheme.withParams({ cellTextColor: '#FF0000' }),
[baseTheme],
);
return <AgGridReact theme={theme} rowData={rows} columnDefs={columns} />;
};Default column definitions
Shared column defaults to reduce repetition:
const defaultColDef: ColDef = {
resizable: true,
filter: true,
flex: 1,
minWidth: 100,
sortable: true,
};
<AgGridReact
theme={theme}
rowData={rows}
columnDefs={columns}
defaultColDef={defaultColDef}
/>Column definition patterns
const columnDefs: ColDef<Item>[] = [
// Flex sizing — fills available space proportionally
{ field: 'name', headerName: 'Name', flex: 2 },
// Fixed width
{ field: 'status', headerName: 'Status', width: 120 },
// Value formatter
{
field: 'createdAt',
headerName: 'Created',
valueFormatter: ({ value }) => new Date(value).toLocaleDateString(),
},
// Cell renderer for custom content
{
field: 'actions',
headerName: '',
cellRenderer: ({ data }) => <Button variant="ghost">Edit</Button>,
width: 80,
sortable: false,
filter: false,
},
];When to use AG Grid vs EDS Table
| Use case | Component |
|---|---|
| Sorting, filtering, resizable/reorderable columns | AgGridReact via fusion-react-ag-grid |
| Simple read-only table, few rows | Table from @equinor/eds-core-react |
| Key-value display | Table or definition list |
Packages and entry points
| Import path | Purpose |
|---|---|
@equinor/fusion-framework-react-ag-grid | Main: AgGridReact, enableAgGrid, useTheme, fusionTheme |
@equinor/fusion-framework-react-ag-grid/community | Re-exports from ag-grid-community (ColDef, events, etc.) |
@equinor/fusion-framework-react-ag-grid/enterprise | Re-exports from ag-grid-enterprise (requires license) |
@equinor/fusion-framework-react-ag-grid/themes | fusionTheme, createTheme, createThemeFromTheme |
TypeScript note
Set "moduleResolution": "bundler" in tsconfig.json to resolve AG Grid types correctly from this package.
Using Analytics
Instrumenting Fusion apps with Fusion Framework analytics.
What the framework provides
Common tracking API; platform handles collection. Portal already includes analytics support — most app work starts with the hook.
Basic tracking
import { useEffect } from 'react';
import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
export const DashboardPage = () => {
const trackFeature = useTrackFeature();
useEffect(() => {
trackFeature('dashboard-page-loaded');
}, [trackFeature]);
return null;
};Track user actions and screen loads
import { useEffect } from 'react';
import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
export const DashboardPage = () => {
const trackFeature = useTrackFeature();
useEffect(() => {
trackFeature('dashboard-page-loaded');
}, [trackFeature]);
return (
<button type="button" onClick={() => trackFeature('dashboard-filter-opened')}>
Open filters
</button>
);
};Send small structured metadata
trackFeature('dashboard-filter-applied', {
filterCount: 3,
section: 'overview',
});Use small, intentional metadata. Never include secrets, tokens, or personal data.
Consistent event names: kebab-case (e.g. dashboard-page-loaded, dashboard-filter-opened, dashboard-filter-applied).
When to instrument
Good analytics events usually capture:
- page or feature entry points
- meaningful user actions such as apply, create, approve, or export
- important workflow milestones or failures
- rollout observation for newly introduced features
Avoid tracking every render or low-value UI noise.
Local testing
To inspect analytics locally: 1. Enable the Log Fusion Analytics feature in the portal profile. 2. Refresh the page. 3. Inspect console output prefixed with Analytics::Adapter::Console.
Advanced configuration
Most apps rely on portal's existing analytics setup. Custom collectors/adapters: configure in app config and check analytics module docs.
Review guidance
- Prefer
useTrackFeature()over ad hoc analytics wrappers unless the project already standardizes one. - Reuse stable event names when extending an existing workflow.
- Keep one event naming scheme across page-load and interaction events.
- Track business-significant interactions, not implementation details.
Relevant sources
- Fusion docs analytics guide
- analytics module documentation
Using Assets and Runtime Configuration
Separating bundled assets from runtime configuration in Fusion Framework apps.
Put runtime values in app config
Use app.config.ts for:
- environment-specific flags and numeric or string settings
- API endpoints and scopes
- values that may differ between
ci,test,prod, or PR environments
import { defineAppConfig } from '@equinor/fusion-framework-cli/app';
export default defineAppConfig((env, _args) => ({
environment: {
logLevel: env.environment === 'ci' ? 0 : 4,
environmentName: env.environment,
},
endpoints: {
myApi: {
url: 'https://api.example.com',
scopes: ['api://my-api/.default'],
},
},
}));Environment-specific files such as app.config.prod.ts override app.config.ts when present.
Use runtime config in config.ts
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
interface AppEnvironment {
logLevel: number;
}
export const configure: AppModuleInitiator = (configurator, env) => {
configurator.configureHttpClient('myApi', {
baseUri: env.config?.getEndpoint('myApi')?.url,
defaultScopes: env.config?.getEndpoint('myApi')?.scopes,
});
const logLevel = (env.config?.environment as AppEnvironment)?.logLevel ?? 4;
configurator.logger.level = logLevel;
};Use runtime config in React components
import { useAppEnvironmentVariables } from '@equinor/fusion-framework-react-app';
export const EnvironmentDisplay = () => {
const { value, complete, error } = useAppEnvironmentVariables();
if (!complete) {
return <p>Loading runtime config...</p>;
}
if (error) {
return <p>Unable to load runtime config</p>;
}
return <pre>{JSON.stringify(value, null, 2)}</pre>;
};Endpoints and service discovery
Use endpoints in app.config.ts for explicit URLs or scopes.
Defining an endpoint key overrides service discovery for the same key (useful for PR environments, but be deliberate).
Static assets
Fusion serves application assets through the app lifecycle and App Proxy path after the app is deployed. Treat images, icons, and other static resources as bundled application assets, not runtime configuration.
Use the project's existing asset import pattern for:
- local images and illustrations
- icons or logos shipped with the app
- static JSON or other bundled resources
Don't put deployment-specific values in bundled assets. Use app.config.ts for per-environment selection instead of hardcoding environment logic in components.
Choosing the right storage
| Concern | Use |
|---|---|
| API base URL, scopes, log level, environment name | app.config.ts |
| Per-user preference | App settings |
| Shareable view state | Bookmarks |
| Images, logos, bundled static files | Application assets in source |
Pitfalls
- Hardcoded URLs that should come from
app.config.ts - Storing secrets in source-controlled config or asset files
- Using app settings for deployment config
- Using bookmarks for non-shareable runtime values
Relevant sources
- Fusion docs app config guide
- Fusion docs app lifecycle guide
- environment variables cookbook
Using Bookmarks
Saving and restoring shareable view state with Fusion Framework bookmarks.
When bookmarks are the right fit
Use bookmarks for:
- filters, pagination, selected entities, and dashboard layout that users want to revisit
- links that should reproduce a complex screen state for another user
- support and debugging flows where you need to load the same view state as the reporter
Do not use bookmarks for:
- secrets, tokens, or personal-only preferences
- large cached datasets
- deployment config or API endpoint selection
Enable the bookmark module
import { enableBookmark } from '@equinor/fusion-framework-react-app/bookmark';
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
export const configure: AppModuleInitiator = (configurator) => {
enableBookmark(configurator);
};Read the current bookmark and restore state
import { useCurrentBookmark } from '@equinor/fusion-framework-react-app/bookmark';
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
interface FiltersBookmark {
query: string;
includeClosed: boolean;
}
export const FiltersPanel = () => {
const [filters, setFilters] = useState<FiltersBookmark>({
query: '',
includeClosed: false,
});
const latestFilters = useRef(filters);
const { currentBookmark } = useCurrentBookmark<FiltersBookmark>(
useCallback(() => latestFilters.current, []),
);
useLayoutEffect(() => {
setFilters({
query: currentBookmark?.payload?.query ?? '',
includeClosed: currentBookmark?.payload?.includeClosed ?? false,
});
}, [currentBookmark]);
useLayoutEffect(() => {
latestFilters.current = filters;
}, [filters]);
return (
<form>
<input
value={filters.query}
onChange={(event) =>
setFilters((current) => ({ ...current, query: event.target.value }))
}
placeholder="Search"
/>
<label>
<input
type="checkbox"
checked={filters.includeClosed}
onChange={(event) =>
setFilters((current) => ({
...current,
includeClosed: event.target.checked,
}))
}
/>
Include closed
</label>
</form>
);
};The payload generator returns serializable state for the bookmark system to save and restore.
Choosing bookmark payload
Good bookmark payloads:
- filters and sort order
- selected IDs or tabs
- current page or panel state
- other serializable view-model data another user can reproduce
Keep route identity in the router. Bookmarks are for page state layered on top of routes.
Multi-page flows
For multi-page bookmark behavior:
- keep navigation source of truth in route params and route structure
- keep bookmark payload focused on the page or workflow state that needs restoring
- if several pages participate in one workflow, define a stable payload shape that each page can read defensively
- validate against the advanced bookmark cookbook before introducing cross-page assumptions
Current API note
Fusion docs highlight useCurrentBookmark as the current hook. Older useBookmark usage exists in some codebases, but the bookmark rework notes mark it as deprecated. Prefer the useCurrentBookmark-based flow when adding new behavior.
Relevant sources
- Fusion docs bookmark guide
cookbooks/app-react-bookmarkcookbooks/app-react-bookmark-advanced- bookmark rework release notes
Using Context
Enabling, configuring, and consuming the Fusion context module.
Context: the entity the user is working with — a project, facility, contract, or other domain object selected in Fusion Portal.
Enable context in config.ts
Not enabled by default. Register with enableContext:
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
import { enableContext } from '@equinor/fusion-framework-module-context';
export const configure: AppModuleInitiator = (configurator) => {
enableContext(configurator, (builder) => {
// Only accept ProjectMaster context items
builder.setContextType(['ProjectMaster']);
});
};Builder options
The builder callback exposes several configuration methods:
| Method | Purpose |
|---|---|
setContextType(types) | Restrict which context types the app accepts |
setContextFilter(fn) | Post-query filter on search results |
setContextParameterFn(fn) | Custom parameter mapping for the search API |
setValidateContext(fn) | Custom validation logic for context items |
setResolveContext(fn) | Custom context resolution (e.g. resolve related items) |
connectParentContext(bool) | Enable/disable sync with the portal's context (default: true) |
setContextClient(client) | Replace the default context API with a custom get/query client |
setContextPathExtractor(fn) | Extract context ID from URL path |
setContextPathGenerator(fn) | Generate URL path from context item |
setResolveInitialContext(fn) | Override initial context resolution on app load |
Minimal setup only needs setContextType. Add other methods as needed.
Read the current context
Use useCurrentContext hook:
import { useCurrentContext } from '@equinor/fusion-framework-react-app/context';
const MyComponent = () => {
const context = useCurrentContext();
if (!context) return <p>No context selected</p>;
return <p>Working on: {context.title}</p>;
};context is undefined when no context is selected. Updates automatically on portal context change.
Use context in data fetching
Common: pass context ID to API queries so data refreshes on context change.
With React Query
import { useQuery } from '@tanstack/react-query';
import { useHttpClient } from '@equinor/fusion-framework-react-app/http';
import { useCurrentContext } from '@equinor/fusion-framework-react-app/context';
/**
* Fetches items scoped to the current context.
*
* @returns A React Query result containing the item list, or disabled when no context is selected.
*/
export const useContextItems = () => {
const context = useCurrentContext();
const client = useHttpClient('my-api');
return useQuery({
queryKey: ['items', context?.id],
queryFn: () => client.json(`/projects/${context?.id}/items`),
enabled: !!context?.id,
});
};With plain HTTP client
import { useEffect, useState } from 'react';
import { useHttpClient } from '@equinor/fusion-framework-react-app/http';
import { useCurrentContext } from '@equinor/fusion-framework-react-app/context';
const MyComponent = () => {
const context = useCurrentContext();
const client = useHttpClient('my-api');
const [items, setItems] = useState([]);
useEffect(() => {
if (!context?.id) return;
client.json(`/projects/${context.id}/items`).then(setItems);
}, [context?.id, client]);
return <ul>{items.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;
};Query related contexts
Access entities related to the current context (e.g. tasks under a project):
import { useMemo } from 'react';
import { EMPTY } from 'rxjs';
import {
type ContextItem,
type ContextModule,
useModuleCurrentContext,
} from '@equinor/fusion-framework-react-module-context';
import { useAppModule } from '@equinor/fusion-framework-react-app';
import { useObservableState } from '@equinor/fusion-observable/react';
/**
* Queries contexts related to the current context.
*
* @param type - Optional context type filter (e.g. `['EquinorTask']`).
* @returns An observable state containing related context items.
*/
export const useRelatedContext = (type?: string[]) => {
const { currentContext } = useModuleCurrentContext();
const provider = useAppModule<ContextModule>('context');
return useObservableState(
useMemo(() => {
if (!currentContext) return EMPTY;
return provider.relatedContexts({
item: currentContext,
filter: { type },
});
}, [provider, currentContext, type]),
);
};Context types
Common context types in the Fusion ecosystem:
| Type | Represents |
|---|---|
ProjectMaster | A project entity |
Facility | A physical facility |
Contract | A contract |
EquinorTask | A task or work order |
The exact types available depend on the Fusion environment and service discovery configuration.
Common patterns
Guard a page that requires context
import type { ReactNode } from 'react';
const ContextGuard = ({ children }: { children: ReactNode }) => {
const context = useCurrentContext();
if (!context) {
return <Banner variant="info">Select a project to continue</Banner>;
}
return <>{children}</>;
};Extract context ID for URL routing
The context module can extract a context ID from the URL path and sync it:
enableContext(configurator, (builder) => {
builder.setContextType(['ProjectMaster']);
builder.setContextPathExtractor((path) => path.split('/')[2]);
builder.setContextPathGenerator((ctx, path) =>
path.replace(/\/context\/[^/]+/, `/context/${ctx.id}`),
);
});Custom context client (bring your own context)
When the app's context doesn't come from the standard Fusion context API — for example it uses a custom backend, a different data model, or needs to query a domain-specific service — replace the default client with setContextClient:
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
import { enableContext } from '@equinor/fusion-framework-module-context';
export const configure: AppModuleInitiator = (configurator) => {
enableContext(configurator, (builder) => {
// Disable parent context sync — this app manages its own context
builder.connectParentContext(false);
// Provide a fully custom client that replaces the default Fusion context API
builder.setContextClient({
get: async (args) => {
const res = await fetch(`/api/my-context/${args.id}`);
return res.json();
},
query: async (args) => {
const res = await fetch(`/api/my-context?q=${args.search}`);
return res.json();
},
});
});
};The custom client must satisfy the context client interface:
| Method | Signature | Purpose |
|---|---|---|
get | (args: { id: string }) => Promise<ContextItem> | Fetch a single context item by ID |
query | (args: { search: string }) => Promise<ContextItem[]> | Search for context items |
Once registered, useCurrentContext(), setCurrentContextByIdAsync(), and queryContextAsync() all use the custom client transparently — component code does not change.
When to use a custom client
- The app uses a domain-specific entity (e.g. wells, assets, campaigns) as context instead of the standard Fusion types
- Context data lives in a separate backend not registered in Fusion service discovery
- The app needs custom search logic (full-text, OData filters, GraphQL) beyond what the Fusion context API supports
- The app must not sync with the portal's context picker (set
connectParentContext(false))
Combining with standard context
If the app should still appear in the portal's context picker but needs extra data, keep connectParentContext(true) (default) and only override get/query to enrich the results:
enableContext(configurator, (builder) => {
builder.setContextType(['ProjectMaster']);
// Enrich standard context with domain-specific data
builder.setContextClient({
get: async (args) => {
const [ctx, extra] = await Promise.all([
fetch(`/api/context/${args.id}`).then((r) => r.json()),
fetch(`/api/my-domain/${args.id}/metadata`).then((r) => r.json()),
]);
return { ...ctx, value: { ...ctx.value, ...extra } };
},
query: (args) =>
fetch(`/api/context?q=${args.search}`).then((r) => r.json()),
});
});What not to do
- Don't bypass the HTTP client: Using
fetch(service.uri + '/path')with a discovered service URI skips token injection. Always useuseHttpClient(name). - Don't store context in local state: The framework manages context state. Read it from
useCurrentContext(), don't copy it intouseState. - Don't hardcode context IDs: Always derive them from
useCurrentContext()or URL parameters.
Authoring Custom Fusion Framework Modules
Reusable cross-cutting modules: lifecycle, configuration, usage.
Fusion docs: Fusion Framework modules Cookbook: Custom module cookbook in Fusion Framework source (packages/modules/)
When to use a custom module
Use when:
- cross-cutting behavior needed by multiple components/routes
- depends on Fusion bootstrap lifecycle (needs framework instance at startup)
- decouple infrastructure (caching, subscriptions, service clients) from components
Not appropriate for:
- single-component state — use React state/context
- single-page data fetching — use React Query hook
- generic utilities — use plain TypeScript module
Module contract
Implement IModule from @equinor/fusion-framework-module:
import type { IModule } from '@equinor/fusion-framework-module';
export const MODULE_NAME = 'myModule' as const; // Unique module key — must not conflict with built-in modules
export interface IMyModuleConfig {
// Configuration shape for your module
apiBaseUrl: string;
}
export interface IMyModule {
// Public API your module exposes to the app
getClient(): MyApiClient;
}
export const module: IModule<IMyModule, IMyModuleConfig> = {
name: MODULE_NAME,
initialize: async ({ config, instance }): Promise<IMyModule> => {
const cfg = await config;
const client = new MyApiClient(cfg.apiBaseUrl);
return { getClient: () => client };
},
};Wiring a custom module into an app
Register in src/config.ts:
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
import { module as myModule } from './modules/myModule';
export const configure: AppModuleInitiator = (configurator) => {
// Built-in modules
configurator.useFrameworkServiceClient('myService');
// Custom module
configurator.addModule(myModule, (moduleConfigurator) => {
moduleConfigurator.setConfig({
apiBaseUrl: 'https://api.example.com',
});
});
};Accessing a custom module in components
Use useAppModule with module name key:
import { useAppModule } from '@equinor/fusion-framework-react-app';
import type { IMyModule } from '../modules/myModule';
import { MODULE_NAME } from '../modules/myModule';
const MyFeatureComponent = () => {
// Type the module by its interface
const myModule = useAppModule<IMyModule>(MODULE_NAME);
const client = myModule.getClient();
// Use the client in a React Query hook or useEffect
};useAppModule is a hook — only inside components/hooks.Module lifecycle
Modules initialized before React mounts:
1. initialize — called once at bootstrap with merged config and parent framework instance 2. Module stored in framework instance under name key 3. Components access via useAppModule(name) after mount
No destroy lifecycle on most modules. Clean up subscriptions in components via useEffect.
File structure
Module lives alongside app source:
src/
modules/
myModule/
index.ts — module definition + exports
MyApiClient.ts — implementation
config.ts — register module hereCommon pitfalls
- Naming conflicts:
namemust not collide with built-in Fusion modules (http,auth,context,navigation,featureFlag,settings,bookmarks,analytics). - Outside React:
useAppModuleis a hook — can't call outside components. Loaders/non-React: useframework.modules.<name>. - Config not awaited:
initializereceivesPromise<IMyModuleConfig>— alwaysawait config. - Over-engineering: single-component need? Use hook/context instead.
When to use fusion-research first
Uncertain about API signatures or built-in module config? Use fusion-research with mcp_fusion_search_framework first.
Example query: mcp_fusion_search_framework → "IModule initialize configure AppModuleInitiator custom module"
Using Feature Flags
Using Fusion Framework feature toggling in React apps.
When feature flags are the right fit
Use feature flags for:
- incremental rollout of features that need to be enabled or disabled at runtime
- temporary toggles that will be removed after rollout is complete
- developer or debug toggles that should persist locally (
createLocalStoragePlugin) - test or override toggles controlled via URL query parameters (
createUrlPlugin)
Do not use feature flags for:
- permanent configuration (use
app.config.tsor App Settings) - per-user preferences that should survive long-term (use App Settings)
- authorization or access control (use Fusion context and role checks)
App-level setup
For most Fusion apps, start with the app-level helper.
// src/config.ts
import { enableFeatureFlag } from '@equinor/fusion-framework-react-app/feature-flag';
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
export enum Feature {
NewDashboard = 'new-dashboard',
}
export const configure: AppModuleInitiator = (appConfigurator) => {
enableFeatureFlag(appConfigurator, [
{
key: Feature.NewDashboard,
title: 'New dashboard',
description: 'Enable the redesigned dashboard layout.',
},
]);
};- Prefer shared enum or string-constant object over raw strings
- Add
titleanddescriptionwhen surfaced to users or code reviews - Use
valueonly when behavior requires more than on/off
Framework-level setup with plugins
When participating in framework-wide providers or needing local persistence and URL override, use the module-level API.
// framework-level configuration (for example in src/framework-config.ts)
import { enableFeatureFlagging } from '@equinor/fusion-framework-module-feature-flag';
import {
createLocalStoragePlugin,
createUrlPlugin,
} from '@equinor/fusion-framework-module-feature-flag/plugins';
export const configureFeatureFlags = (configurator) => {
enableFeatureFlagging(configurator, (builder) => {
builder.addPlugin(
createLocalStoragePlugin([{ key: 'fusionDebug', title: 'Fusion debug log' }]),
);
builder.addPlugin(createUrlPlugin(['fusionDebug']));
});
};createLocalStoragePlugin— persists toggle state inlocalStorage; suitable for developer or debug flags.createUrlPlugin— reads flag state from the URL query string; useful for QA override and test automation.
Consuming flags in components
Use useFeature in React components:
// src/components/Dashboard.tsx
import { useFeature } from '@equinor/fusion-framework-react-app/feature-flag';
import { Feature } from '../config';
const Dashboard = () => {
const { feature, toggleFeature } = useFeature(Feature.NewDashboard);
if (!feature?.enabled) {
return <LegacyDashboard />;
}
return <NewDashboard />;
};- Access
feature.enabledfor boolean gating - Access
feature.valuefor typed configuration value - Call
toggleFeature()for user-controlled toggling - Guard against
featurebeingundefined; use optional chaining or null check
Provider-based hook variant
Provider-based variant for explicit provider control:
import { useFeature } from '@equinor/fusion-framework-react/feature-flag';
const value = useFeature(provider, 'my-flag-key');Use only at framework level outside the app package scope.
Known API ambiguity
Public sources show inconsistent spelling for the read-only flag property:
- Some sources use
readonly - Older docs mention
readOnly
Verify against local types or Fusion MCP before finalizing code that reads this property.
Rollout and cleanup
Feature flags are temporary — every flag needs an owner and a removal plan.
- Define an owner in a comment or in the flag
description - State removal milestone at which the flag will be removed
- Test both paths — app must work with flag on and off
- Remove completely — delete flag definition, all consumers, and dead code branches in one PR
- Avoid long-lived flags — flags that outlive rollout become maintenance debt
Rollout checklist
- [ ] Flag key defined in a shared enum or constant.
- [ ]
titleanddescriptionfilled in. - [ ] Entry point chosen:
enableFeatureFlag(app) orenableFeatureFlagging(framework/plugin). - [ ] Plugin added if local persistence or URL override is required.
- [ ] Component reads
feature?.enabledwith null-safe access. - [ ] Both enabled and disabled rendering paths tested.
- [ ] Owner and removal milestone documented.
- [ ]
readonlyvsreadOnlyverified against local types.
MCP-grounded research
For live API references and up-to-date examples, query Fusion MCP:
mcp_fusion_search_framework: "feature flag enableFeatureFlag useFeature"
mcp_fusion_search_framework: "feature toggling plugin createLocalStoragePlugin createUrlPlugin"If Fusion MCP is unavailable, use the public Fusion Framework sources below.
Public source map
- Module README: https://github.com/equinor/fusion-framework/blob/main/packages/modules/feature-flag/README.md
- App guide: https://github.com/equinor/fusion-framework/blob/main/vue-press/src/guide/app/feature-flag.md
- Cookbook example config: https://github.com/equinor/fusion-framework/blob/main/cookbooks/app-react-feature-flag/src/config.ts
- Cookbook component example: https://github.com/equinor/fusion-framework/blob/main/cookbooks/app-react-feature-flag/src/FeatureFlags.tsx
- App hook: https://github.com/equinor/fusion-framework/blob/main/packages/react/app/src/feature-flag/useFeature.ts
- Framework hook: https://github.com/equinor/fusion-framework/blob/main/packages/react/framework/src/feature-flag/useFeature.ts
Using Framework Modules
How to access Fusion Framework modules (context, auth, navigation, feature flags, settings, environment, bookmarks, analytics) in components.
Module access patterns
Most module hooks are available from sub-path imports of @equinor/fusion-framework-react-app. Runtime config access is currently exposed from the package root:
import { useAppEnvironmentVariables, useAppModule } from '@equinor/fusion-framework-react-app'; // root exports
import { useHttpClient } from '@equinor/fusion-framework-react-app/http'; // HTTP
import { useCurrentContext } from '@equinor/fusion-framework-react-app/context'; // context
import { useCurrentAccount } from '@equinor/fusion-framework-react-app/msal'; // auth
import { useRouter } from '@equinor/fusion-framework-react-app/navigation'; // routing
import { useFeature } from '@equinor/fusion-framework-react-app/feature-flag'; // flags
import { useAppSetting } from '@equinor/fusion-framework-react-app/settings'; // settings
import { useCurrentBookmark } from '@equinor/fusion-framework-react-app/bookmark'; // bookmark
import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics'; // analyticsContext
Access the current Fusion context (project, facility, etc.):
import { useCurrentContext } from '@equinor/fusion-framework-react-app/context';
const MyComponent = () => {
const context = useCurrentContext();
if (!context) return <p>No context selected</p>;
return <p>Selected: {context.title}</p>;
};Authentication (MSAL)
The MSAL module is configured by the host portal — apps only consume the hooks:
import { useCurrentAccount, useAccessToken } from '@equinor/fusion-framework-react-app/msal';
const UserInfo = () => {
const user = useCurrentAccount();
const { token } = useAccessToken({ scopes: ['User.Read'] });
if (!user) return <p>Not signed in</p>;
return <p>Welcome, {user.name}</p>;
};Navigation
import { useRouter, useNavigationModule } from '@equinor/fusion-framework-react-app/navigation';
const MyComponent = () => {
const router = useRouter();
// router.navigate('/path')
};Feature flags
import { useFeature } from '@equinor/fusion-framework-react-app/feature-flag';
// In config.ts — enable a flag
configurator.enableFeatureFlag({ key: 'new-dashboard', title: 'New Dashboard' });
// In components — read the flag
const MyComponent = () => {
const [isEnabled] = useFeature('new-dashboard');
if (!isEnabled) return null;
return <NewDashboard />;
};App settings
Per-user settings stored by the Fusion platform:
import { useAppSetting, useAppSettings } from '@equinor/fusion-framework-react-app/settings';
const MyComponent = () => {
const [theme, setTheme] = useAppSetting('theme', 'light');
// theme is the current value, setTheme persists a new value
};Use app settings for per-user preferences that should survive between sessions. See using-settings.md for how to decide between settings, bookmarks, and runtime config.
Environment variables
Access runtime configuration from app.config.ts:
import { useAppEnvironmentVariables } from '@equinor/fusion-framework-react-app';
const MyComponent = () => {
const { value, complete, error } = useAppEnvironmentVariables();
// value contains keys defined in app.config.ts environment: {}
};Use app.config.ts for deployment-specific values and endpoint definitions. See using-assets-and-environment.md for config/file-placement guidance.
Bookmarks
import { useCurrentBookmark } from '@equinor/fusion-framework-react-app/bookmark';Use bookmarks for shareable, restorable view state such as filters, selected tabs, and dashboard layout. Prefer useCurrentBookmark for new work. See using-bookmarks.md for module setup and payload guidance.
Analytics
import { useEffect } from 'react';
import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
const DashboardPage = () => {
const trackFeature = useTrackFeature();
useEffect(() => {
trackFeature('dashboard-page-loaded');
}, [trackFeature]);
return null;
};The portal provides analytics support out of the box. Use the hook for feature and interaction events, and see using-analytics.md for event design and local testing guidance.
Generic module access
Access any module by key when a dedicated hook is not available:
import { useAppModule, useAppModules } from '@equinor/fusion-framework-react-app';
const auth = useAppModule('auth');
const allModules = useAppModules();Using the Fusion People Service
Fusion People API: people discovery, person details, integration.
Source: @equinor/fusion-react-person + Fusion Framework people service Storybook: equinor.github.io/fusion-react-components — person
When to use the people service
Use when:
- search/pick people (assignees, owners, reviewers)
- display name, avatar, department, role
- show manager/reporting relationship
- person column in AG Grid
Most cases: @equinor/fusion-react-person components handle it — provide azureId, they resolve the rest.
The preferred path: let components resolve people
Components resolve via azureId (preferred) or upn. azureId is stable, unambiguous.
import { PersonAvatar, PersonCard, PersonListItem } from '@equinor/fusion-react-person';
// Correct — pass azureId, let the component resolve the rest
<PersonAvatar azureId={item.ownerAzureId} />
<PersonCard azureId={item.ownerAzureId} />
<PersonListItem azureId={item.ownerAzureId} />Don't fetch person data manually and pass as props — components handle loading, caching, errors.
See references/using-fusion-react-components.md for full component usage examples (pickers, card, list item, ag-grid cell).
Storing person references
Store only azureId in data model. Resolve display at render via components.
// Data layer — store azureId only
interface WorkItem {
id: string;
title: string;
ownerAzureId: string; // Azure AD object ID (UUID)
}
// UI layer — resolve at render time
<PersonCard azureId={item.ownerAzureId} />Don't cache or persist resolved PersonInfo objects — components re-resolve from the API.
People search via PersonPicker / PeoplePicker
For user selection, use picker components — query API automatically on input.
import { PersonPicker, type PersonInfo } from '@equinor/fusion-react-person';
import { useState } from 'react';
// Single-person picker
const AssigneeField = ({
onSelect,
}: {
onSelect: (azureId: string) => void;
}) => {
const [person, setPerson] = useState<PersonInfo | null>(null);
return (
<PersonPicker
person={person}
placeholder="Search for a person…"
subtitle="jobTitle"
onPersonAdded={(e) => {
setPerson(e.detail);
onSelect(e.detail.azureId);
}}
onPersonRemoved={() => {
setPerson(null);
onSelect('');
}}
/>
);
};Event pattern: picker components use custom DOM events (PersonAddedEvent,PersonRemovedEvent). Access the person viae.detail, not the argument directly.
Integration points
| Need | Component / hook |
|---|---|
| Display a person (avatar, hover details) | PersonAvatar |
| Full person detail (card, panel) | PersonCard |
| Person in a list row | PersonListItem |
| Person in an AG Grid column | PersonCell |
| Let user pick one person | PersonPicker or PersonSelect |
| Let user pick multiple people | PeoplePicker |
| Display a selected collection | PeopleViewer |
System accounts
Person search excludes system accounts by default. To include (e.g. service principals):
<PersonPicker
systemAccounts={true}
onPersonAdded={(e) => onSelect(e.detail.azureId)}
onPersonRemoved={() => onSelect('')}
/>Non-goals
- Do not build a custom people API client using
useHttpClient— the framework and components handle this internally. - Do not manage people search state manually — the picker components handle input, debouncing, and results.
- Do not pass
PersonInfoobjects directly into non-picker components — passazureIdand let the component resolve.
Related skills
How it compares
Use fusion-developer-app only inside Fusion Framework React codebases; generic React skills miss Equinor module conventions.
FAQ
What tasks does fusion-developer-app cover?
fusion-developer-app covers building Fusion Framework React features: adding components, pages, hooks, services, API endpoint wiring, and module configuration, with app-scoped research to pick correct Fusion surfaces.
What should fusion-developer-app not be used for?
fusion-developer-app should not be used for issue authoring, skill authoring, CI/CD configuration, backend service changes, or general Fusion documentation unrelated to in-app implementation.