Skip to Content
DocsForms in O3Using forms in applications

Using forms in applications

Once you’ve built a form using the Form Builder or converted an existing HTML Form Entry form, you’ll need to integrate it into your application. The React Form Engine provides flexible ways to render forms in different contexts, from patient chart workspaces to custom applications.

Overview

The React Form Engine can be used in two main ways:

  1. Form Renderer Extension - A pre-built component that handles form loading, rendering, and workspace integration. This is the easiest way to render forms in workspaces.
  2. FormEngine Component - The core component that provides full control over form rendering, submission, and behavior.

Rendering forms in workspaces

The patient chart already provides a form entry workspace through @openmrs/esm-patient-forms-app. For most patient-chart workflows, launch patient-form-entry-workspace with a form object and optional encounter details instead of registering your own workspace:

import { launchWorkspace2 } from '@openmrs/esm-framework'; import { type Form } from '@openmrs/esm-patient-common-lib'; function launchMyForm(form: Form, encounterUuid?: string) { launchWorkspace2('patient-form-entry-workspace', { form, encounterUuid, additionalProps: { mode: encounterUuid ? 'edit' : 'enter', }, }); }
ℹ️

patient-form-entry-workspace reads patient, patientUuid, visitContext, and mutateVisitContext from the patient chart workspace group. Launch it from inside the patient chart, or build your own workspace wrapper if your form needs to run outside that context.

The workspace wraps the form in Workspace2, renders the visit-context-header-slot, and then renders either an HTML Form Entry iframe or the React Form Engine through the form-widget-slot.

Creating a custom form workspace

ℹ️

The react-form-engine-widget extension is registered by @openmrs/esm-form-engine-app in the form-widget-slot. You only need a custom workspace when you want a different workspace shell, custom data refresh behavior, or a non-patient-chart rendering context.

If you do create your own workspace for the patient chart, register it with Workspace v2 routes and put its window in the patient-chart group:

{ "workspaces2": [ { "name": "my-form-workspace", "component": "myFormWorkspace", "window": "my-form-window" } ], "workspaceWindows2": [ { "name": "my-form-window", "group": "patient-chart", "width": "extra-wide", "canMaximize": true } ] }

Then render form-widget-slot from a Workspace2 shell and pass the state expected by the form renderer:

import React, { useState } from 'react'; import { ExtensionSlot, Workspace2 } from '@openmrs/esm-framework'; import { useSWRConfig } from 'swr'; import { invalidateVisitAndEncounterData, type PatientWorkspace2DefinitionProps, type FormRendererProps, } from '@openmrs/esm-patient-common-lib'; interface MyFormWorkspaceProps { formUuid: string; encounterUuid?: string; } export function MyFormWorkspace({ closeWorkspace, workspaceProps: { formUuid, encounterUuid }, groupProps: { patient, patientUuid, visitContext, mutateVisitContext }, }: PatientWorkspace2DefinitionProps<MyFormWorkspaceProps, object>) { const { mutate: globalMutate } = useSWRConfig(); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const state: FormRendererProps = { formUuid, patient, patientUuid, encounterUuid: encounterUuid ?? '', visit: visitContext, visitUuid: visitContext?.uuid, closeWorkspace, closeWorkspaceWithSavedChanges: () => { mutateVisitContext?.(); invalidateVisitAndEncounterData(globalMutate, patientUuid); return closeWorkspace({ discardUnsavedChanges: true }); }, setHasUnsavedChanges, additionalProps: { mode: encounterUuid ? 'edit' : 'enter', }, }; return ( <Workspace2 title="My form" hasUnsavedChanges={hasUnsavedChanges}> <ExtensionSlot name="form-widget-slot" state={state} /> </Workspace2> ); }

closeWorkspaceWithSavedChanges is part of the FormRendererProps state passed to form-widget-slot. Use it for post-save refresh work before closing the workspace. Workspace v2’s closeWorkspace still handles ordinary cancel or close behavior.

Launch the custom workspace with Workspace v2:

import { launchWorkspace2 } from '@openmrs/esm-framework'; function launchMyCustomWorkspace(formUuid: string) { launchWorkspace2('my-form-workspace', { formUuid, }); }

This launch example assumes it runs from a patient-chart extension or page, so the patient-chart group props are available to the workspace.

💡

The form renderer extension automatically handles form schema loading, error states, and workspace lifecycle management. It’s the recommended approach for most use cases.

Using FormEngine directly

For more control over form rendering and behavior, you can use the FormEngine component directly from @openmrs/esm-form-engine-lib. This is useful when you need custom submission handling, want to render forms outside of workspaces, or need fine-grained control over form behavior.

Basic usage

import React from 'react'; import { type Encounter } from '@openmrs/esm-framework'; import { FormEngine, type FormSchema } from '@openmrs/esm-form-engine-lib'; interface MyFormComponentProps { patientUuid: string; formSchema: FormSchema; } export function MyFormComponent({ patientUuid, formSchema }: MyFormComponentProps) { const handleSubmit = (encounters: Array<Encounter>) => { console.log('Form submitted:', encounters); // Handle form submission }; return ( <FormEngine patientUUID={patientUuid} formJson={formSchema} onSubmit={handleSubmit} /> ); }

Loading form schema from UUID

If you have a form UUID instead of the schema, simply pass it to the formUUID prop. The FormEngine will automatically load the schema:

import React from 'react'; import { FormEngine } from '@openmrs/esm-form-engine-lib'; interface MyFormComponentProps { patientUuid: string; formUuid: string; } export function MyFormComponent({ patientUuid, formUuid }: MyFormComponentProps) { return ( <FormEngine patientUUID={patientUuid} formUUID={formUuid} /> ); }
💡

The FormEngine handles loading states and errors internally. You don’t need to manually fetch the form schema unless you have a specific use case that requires it.

Form props and configuration

The FormEngine component accepts the following props:

Required props

  • patientUUID (string) - The UUID of the patient for whom the form is being filled

Optional props

  • formUUID (string) - The UUID of the form to load from the server. If provided, the form schema will be loaded automatically. Cannot be provided together with formJson - you must use either formUUID or formJson, not both.
  • formJson (FormSchema) - The form schema object. Use this when you already have the schema loaded or want to use a custom schema.
  • encounterUUID (string) - The UUID of an existing encounter to edit. When provided, the form will be pre-filled with encounter data and rendered in edit mode.
  • visit (Visit) - The visit object to associate with a submitted encounter. The patient chart form workspace supplies the active visit for new forms, or the encounter’s own visit when editing an existing encounter.
  • mode ('enter' | 'edit' | 'view' | 'embedded-view') - The rendering mode for the form. See Form rendering modes below.
  • formSessionIntent (string) - The form intent used while refining the schema. It applies intent-specific defaults and behaviours such as availableIntents.defaultPage, field readonly, field hide, field defaultValue, and subform intent selection. The form renderer extension passes '*' by default; when using FormEngine directly, pass '*' explicitly if you want wildcard intent behavior.
  • onSubmit (function) - Callback function called when the form is successfully submitted. Receives an array of processor results. For forms that use EncounterFormProcessor, those results are created or updated encounters.
  • onCancel (function) - Callback function called when the user cancels the form.
  • handleClose (function) - Callback function called when the form should be closed.
  • handleConfirmQuestionDeletion (function) - Custom handler for confirming deletion of repeatable questions. Should return a Promise that resolves when deletion is confirmed.
  • markFormAsDirty (function) - Callback function called when the form’s dirty state changes. Useful for workspace integration to prompt before closing.
  • hideControls (boolean) - Hide form action buttons (Save, Cancel). Useful for embedded views or custom UI.
  • hidePatientBanner (boolean) - Hide the patient banner that appears in ultra-wide workspaces.
  • preFilledQuestions (Record<string, string | number | Date | boolean | Array<string>>) - Pre-fill specific questions with values. Keys are question IDs, values are the pre-filled values.

Common prop combinations

Basic form with UUID:

<FormEngine patientUUID={patientUuid} formUUID={formUuid} />

Editing an existing encounter:

<FormEngine patientUUID={patientUuid} formUUID={formUuid} encounterUUID={encounterUuid} mode="edit" />

Form with workspace integration:

import React, { useState } from 'react'; import { Workspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework'; import { FormEngine } from '@openmrs/esm-form-engine-lib'; function MyDirectFormWorkspace({ closeWorkspace, workspaceProps: { patientUuid, formUuid }, }: Workspace2DefinitionProps<{ patientUuid: string; formUuid: string }>) { const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); return ( <Workspace2 title="My form" hasUnsavedChanges={hasUnsavedChanges}> <FormEngine patientUUID={patientUuid} formUUID={formUuid} onSubmit={() => closeWorkspace({ discardUnsavedChanges: true })} handleClose={() => closeWorkspace()} markFormAsDirty={setHasUnsavedChanges} /> </Workspace2> ); }

Form rendering modes

The React Form Engine supports four rendering modes, each suited for different use cases:

Enter mode (default)

Mode: 'enter' or undefined

Use case: Creating new form entries

Behavior:

  • Form is rendered in read-write mode
  • All fields are editable
  • Form can be submitted to create a new encounter
  • Automatically selected when no encounterUUID is provided and mode is not specified
<FormEngine patientUUID={patientUuid} formUUID={formUuid} mode="enter" // or omit for default />

Edit mode

Mode: 'edit'

Use case: Editing existing form entries

Behavior:

  • Form is rendered in read-write mode
  • Form is pre-filled with data from the existing encounter
  • Form can be submitted to update the encounter
  • Automatically selected when encounterUUID is provided and mode is not specified
<FormEngine patientUUID={patientUuid} formUUID={formUuid} encounterUUID={encounterUuid} mode="edit" // or omit when encounterUUID is provided />

View mode

Mode: 'view'

Use case: Displaying form data in read-only mode

Behavior:

  • Form is rendered in read-only mode
  • All fields are disabled
  • Form cannot be submitted
  • Save is disabled or hidden, and the close/cancel control closes the form
<FormEngine patientUUID={patientUuid} formUUID={formUuid} encounterUUID={encounterUuid} mode="view" />

Embedded view mode

Mode: 'embedded-view'

Use case: Displaying form data in a condensed widget or card

Behavior:

  • Form is rendered in read-only mode
  • Sidebar navigation and bottom action buttons are hidden
  • Patient banner is hidden
  • Empty transient fields, and empty unanswered fields configured to be hidden in read-only forms, are omitted
  • Compact layout suitable for embedding in other components
<FormEngine patientUUID={patientUuid} formUUID={formUuid} encounterUUID={encounterUuid} mode="embedded-view" hideControls={true} />

Form submission and response handling

When a form is submitted, the onSubmit callback receives an array of processor results. For forms that use EncounterFormProcessor, each result is an encounter that was created or updated.

Handling form submission

import { type Encounter } from '@openmrs/esm-framework'; function MyFormWorkspace({ patientUuid, formUuid }: Props) { const handleSubmit = (encounters: Array<Encounter>) => { const encounter = encounters[0]; // Handle successful submission console.log('Encounter created:', encounter.uuid); console.log('Observations:', encounter.obs); // Close workspace or navigate closeWorkspace({ discardUnsavedChanges: true }); // Refresh related encounter, visit, or workspace data here. }; return ( <FormEngine patientUUID={patientUuid} formUUID={formUuid} onSubmit={handleSubmit} /> ); }

Error handling

The Form Engine handles validation errors automatically and displays them inline. The onSubmit callback is only called after successful form submission. If submission fails, the Form Engine handles the error internally and displays it to the user - your onSubmit callback will not be called.

import { type Encounter } from '@openmrs/esm-framework'; function MyFormWorkspace({ patientUuid, formUuid }: Props) { const handleSubmit = (encounters: Array<Encounter>) => { // This callback is only called if submission succeeds // The Form Engine handles errors internally console.log('Form submitted successfully:', encounters); closeWorkspace({ discardUnsavedChanges: true }); }; return ( <FormEngine patientUUID={patientUuid} formUUID={formUuid} onSubmit={handleSubmit} /> ); }

Complete example: Form workspace

Here’s a complete example of a custom form workspace that integrates with the patient chart:

import React, { useMemo, useState } from 'react'; import { ExtensionSlot, Workspace2 } from '@openmrs/esm-framework'; import { useSWRConfig } from 'swr'; import { invalidateVisitAndEncounterData, type FormRendererProps, type PatientWorkspace2DefinitionProps, } from '@openmrs/esm-patient-common-lib'; interface MyFormWorkspaceProps { formUuid: string; encounterUuid?: string; } export function MyFormWorkspace({ closeWorkspace, workspaceProps: { formUuid, encounterUuid }, groupProps: { patient, patientUuid, visitContext, mutateVisitContext }, }: PatientWorkspace2DefinitionProps<MyFormWorkspaceProps, object>) { const { mutate: globalMutate } = useSWRConfig(); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const state = useMemo( (): FormRendererProps => ({ formUuid, patient, patientUuid, visit: visitContext, visitUuid: visitContext?.uuid, encounterUuid: encounterUuid ?? '', closeWorkspace, closeWorkspaceWithSavedChanges: () => { mutateVisitContext?.(); invalidateVisitAndEncounterData(globalMutate, patientUuid); return closeWorkspace({ discardUnsavedChanges: true }); }, setHasUnsavedChanges, additionalProps: { mode: encounterUuid ? 'edit' : 'enter', }, }), [ formUuid, globalMutate, patient, patientUuid, visitContext, encounterUuid, closeWorkspace, mutateVisitContext, setHasUnsavedChanges, ] ); return ( <Workspace2 title="My form" hasUnsavedChanges={hasUnsavedChanges}> <ExtensionSlot name="form-widget-slot" state={state} /> </Workspace2> ); }

Pre-filling form questions

You can pre-fill specific questions in a form using the preFilledQuestions prop:

<FormEngine patientUUID={patientUuid} formUUID={formUuid} preFilledQuestions={{ 'vitals-weight': 70, 'vitals-height': 175, 'chief-complaint': 'Patient reports headache' }} />

The keys in preFilledQuestions should match the id property of the questions in your form schema. When you pass values through the patient chart form renderer extension, keep them as strings because its shared FormRendererProps type currently narrows preFilledQuestions to Record<string, string>.

Form session intent

The formSessionIntent prop selects the intent-specific behaviours defined in the form schema. Use it when a schema has different defaults, readonly or hidden fields, default pages, or subform behaviours for different workflows. The patient chart form renderer extension defaults this to '*', but direct FormEngine usage leaves it unset unless you pass a value:

<FormEngine patientUUID={patientUuid} formUUID={formUuid} formSessionIntent="VITALS" // Apply schema behaviours for the VITALS intent />

Next steps

Last updated on