Import Custom React Components
The low-code app editor ships with dozens of prebuilt components you can drag onto the canvas. When the built-in catalogue doesn’t cover what you need — a bespoke chart, a third-party widget, a component with very specific theming — you can bring your own, written in React.
React & Orvanta
Section titled “React & Orvanta”If you’re looking for a way to:
- Build a complete React or Svelte app with Orvanta backend runnables, see Full-code apps.
- Import whole React apps to Orvanta, see React app import.
Once imported, a custom component behaves like any other component on the canvas: it can read component inputs and write outputs other components subscribe to.
Reasons to reach for a custom component instead of the built-in catalogue:
- Reusing React components you’ve already written elsewhere in your stack.
- Full control over theming, inputs, and outputs for more complex interactions than a built-in component supports.
- Embedding something inherently complex, like a code editor.
If a single component isn’t the right unit — you want a whole custom frontend — build a full-code app in React or Svelte connected to Orvanta backend runnables instead.
What Orvanta expects you to upload
Section titled “What Orvanta expects you to upload”A custom component is a single self-contained IIFE bundle that registers a render function under a global. Orvanta loads it into the page at runtime and calls it:
- Before your bundle runs, Orvanta loads React and ReactDOM as UMD globals from the React version you declare at upload time. Your bundle must therefore treat
reactandreact-domas external and read theReactandReactDOMglobals — do not bundle your own copy. - Your bundle must assign a render function to
globalThis.orvanta[<component name>]. If that key is missing, the editor reports that the component is ill-defined. - Orvanta calls that function once with
{ id, render, passSetters, setOutput }:id— the id of a<div>Orvanta has already placed on the canvas. Mount into it.render— whether the component is currently visible.passSetters(setters)— call this with{ onRender, onInput }so Orvanta can push visibility changes and the component’s evaluated input into your component.setOutput(value)— call this to publish the component’soutput, which other components can subscribe to.
- CSS must be injected by JS. There is no second file to upload.
Anything that satisfies that contract works. The setup below is the shortest thing that does.
Set up the project
Section titled “Set up the project”Create an empty directory and install the toolchain:
npm install react@18.2.0 react-dom@18.2.0npm install -D vite @vitejs/plugin-react vite-plugin-css-injected-by-js typescript @types/react @types/react-domAdd vite.config.ts:
import { defineConfig } from 'vite'import react from '@vitejs/plugin-react'import cssInjectedByJs from 'vite-plugin-css-injected-by-js'
// Must match the name you give the component in Orvanta.// Dashes and spaces are normalised to underscores on both sides.const COMPONENT_NAME = 'my_component'
export default defineConfig({ // Orvanta supplies React as a UMD global, so use the classic JSX runtime. plugins: [react({ jsxRuntime: 'classic' }), cssInjectedByJs()], define: { __COMPONENT_NAME__: JSON.stringify(COMPONENT_NAME) }, build: { lib: { entry: 'src/main.tsx', formats: ['iife'], name: COMPONENT_NAME, fileName: () => 'index.iife.js' }, rollupOptions: { external: ['react', 'react-dom', 'react-dom/client'], output: { globals: { react: 'React', 'react-dom': 'ReactDOM', 'react-dom/client': 'ReactDOM' } } } }})Add src/main.tsx — your component, plus the registration Orvanta looks for:
import React, { useEffect, useState } from 'react'import { createRoot } from 'react-dom/client'
declare const __COMPONENT_NAME__: string
type Props = { id: string render: boolean passSetters: (setters: { onRender: (r: boolean) => void; onInput: (i: any) => void }) => void setOutput: (output: any) => void}
function MyComponent({ passSetters, setOutput }: Omit<Props, 'id'>) { const [input, setInput] = useState<any>(undefined) const [visible, setVisible] = useState(true)
useEffect(() => { passSetters({ onRender: setVisible, onInput: setInput }) }, [passSetters])
if (!visible) return null
return ( <div> <p>Input from Orvanta: {JSON.stringify(input)}</p> <button onClick={() => setOutput({ clickedAt: Date.now() })}>Emit an output</button> </div> )}
;(globalThis as any).orvanta ??= {};(globalThis as any).orvanta[__COMPONENT_NAME__] = (props: Props) => { createRoot(document.getElementById(props.id)!).render( <MyComponent render={props.render} passSetters={props.passSetters} setOutput={props.setOutput} /> )}Replace the body of MyComponent with whatever you actually want to render. To iterate locally, add an index.html and a second entry that renders MyComponent directly and run vite — only the built bundle matters to Orvanta.
Build and upload
Section titled “Build and upload”Build the bundle:
npx vite buildThis writes a single self-contained bundle to dist/index.iife.js. Because React is external, the bundle stays small — it contains your component and nothing else.
Then upload it in the app editor: open the components panel on the right, scroll to Custom Components, and click Add new. In the drawer, set Custom Component name to match COMPONENT_NAME in vite.config.ts (dashes and spaces are normalised to underscores on both sides), select dist/index.iife.js as the File, leave React Component on, and set the React version to match the react version you installed (18.2.0 above). Click Add Custom Component.
The component is saved as an app_custom resource at f/app_custom/<component_name>, so your user needs write access to the app_custom folder. Once saved it appears under Custom Components in the components panel and can be dragged onto the canvas like any built-in component.