Forms - Admin Components

In this guide, you'll learn how to create forms that match the Medusa Admin's design conventions.

The Medusa Admin has two types of forms:

  1. Create forms, created using the FocusModal UI component.
  2. Edit or update forms, created using the Drawer UI component.

This guide explains how to create these two form types following the Medusa Admin's conventions.

Note: These forms are built since Medusa v2.21.0 with the Form component that the @medusajs/dashboard package exports. Before that version, you had to compose the fields with FormProvider and Controller from react-hook-form yourself.

Form Tooling#

The Medusa Admin uses the following tools to build the forms:

  1. The Form component from @medusajs/dashboard/components, which renders a field's label, control, hint, and error message with the Medusa Admin's styling.
  2. react-hook-form to easily build forms and manage their states.
  3. Zod to validate the form's fields.

All of these are available in your project, so you don't have to install them to use them.

The Form component wraps react-hook-form's FormProvider, and has the following sub-components:

Component

Description

Form.Field

Registers a field in the form. It accepts the same props as react-hook-form's Controller.

Form.Item

Wraps a field's label, control, hint, and error message.

Form.Label

Shows the field's label. It accepts an optional prop to mark the field as optional, and tooltip and icon props.

Form.Control

Wraps the field's input and connects it to the form's accessibility and error state.

Form.Hint

Shows a hint below the field.

Form.ErrorMessage

Shows the field's validation error, if any.


Create Form#

In this section, you'll build a form component to create an item of a resource.

Full Component

Unlike other components in this documentation, this form component isn't reusable. You have to create one for every resource that has a create form in the admin.

Start by creating the file src/admin/components/create-form.tsx in which you'll create the form.

Create Validation Schema#

In src/admin/components/create-form.tsx, create a validation schema with Zod for the form's fields:

src/admin/components/create-form.tsx
1import * as zod from "zod"2
3const schema = zod.object({4  name: zod.string(),5})

The form in this guide is simple, it only has a required name field, which is a string.

Initialize Form#

Next, you'll initialize the form using react-hook-form.

Add to src/admin/components/create-form.tsx the following:

src/admin/components/create-form.tsx
1// other imports...2import { useForm } from "react-hook-form"3
4// validation schema...5
6export const CreateForm = () => {7  const form = useForm<zod.infer<typeof schema>>({8    defaultValues: {9      name: "",10    },11  })12
13  const handleSubmit = form.handleSubmit(({ name }) => {14    // TODO: submit to backend15    console.log(name)16  })17
18  // TODO render form19}

You create the CreateForm component. For now, it uses useForm from react-hook-form to initialize a form.

You also define a handleSubmit function to perform an action when the form is submitted.

You can replace the content of the function with sending a request to Medusa's routes. Refer to this guide for more details on how to do that.

Render Components#

You'll now add a return statement that renders the focus modal where the form is shown.

Replace // TODO render form with the following:

src/admin/components/create-form.tsx
1// other imports...2import { 3  FocusModal,4  Heading,5  Input,6  Button,7} from "@medusajs/ui"8import { Form } from "@medusajs/dashboard/components"9
10export const CreateForm = () => {11  // ...12
13  return (14    <FocusModal>15      <FocusModal.Trigger asChild>16        <Button>Create</Button>17      </FocusModal.Trigger>18      <FocusModal.Content>19        <Form {...form}>20          <form21            onSubmit={handleSubmit}22            className="flex h-full flex-col overflow-hidden"23          >24            <FocusModal.Header>25              <div className="flex items-center justify-end gap-x-2">26                <FocusModal.Close asChild>27                  <Button size="small" variant="secondary">28                    Cancel29                  </Button>30                </FocusModal.Close>31                <Button type="submit" size="small">32                  Save33                </Button>34              </div>35            </FocusModal.Header>36            <FocusModal.Body>37              <div className="flex flex-1 flex-col items-center overflow-y-auto">38                <div className="mx-auto flex w-full max-w-[720px] flex-col gap-y-8 px-2 py-16">39                  <div>40                    <Heading className="capitalize">41                      Create Item42                    </Heading>43                  </div>44                  <div className="grid grid-cols-2 gap-4">45                    <Form.Field46                      control={form.control}47                      name="name"48                      render={({ field }) => (49                        <Form.Item>50                          <Form.Label>Name</Form.Label>51                          <Form.Control>52                            <Input {...field} />53                          </Form.Control>54                          <Form.ErrorMessage />55                        </Form.Item>56                      )}57                    />58                  </div>59                </div>60              </div>61            </FocusModal.Body>62          </form>63        </Form>64      </FocusModal.Content>65    </FocusModal>66  )67}

You render a focus modal, with a trigger button to open it.

In the FocusModal.Content component, you wrap the content with the Form component, spreading into it the form you initialized earlier. Form provides the form's state to every field below it.

In the Form component, you add a form element passing it the handleSubmit function you created earlier as the handler of the onSubmit event.

In the FocusModal.Header component, you add buttons to save or cancel the form submission.

Finally, you render the form's fields inside the FocusModal.Body. Each field is a Form.Field component, which accepts the form's control, the field's name, and a render function.

In the render function, you wrap the field in a Form.Item component, then add:

  • A Form.Label component showing the field's label.
  • A Form.Control component wrapping the input, which connects the input to the form's accessibility and error state.
  • A Form.ErrorMessage component showing the field's validation error, if any.

Since Form.ErrorMessage shows the errors of your Zod schema, you don't have to render validation messages yourself.

Use Create Form Component#

You can use the CreateForm component in your widget or UI route.

For example, create the widget src/admin/widgets/product-widget.tsx with the following content:

src/admin/widgets/product-widget.tsx
1import { defineWidgetConfig } from "@medusajs/admin-sdk"2import { CreateForm } from "../components/create-form"3import { Container } from "../components/container"4import { Header } from "../components/header"5
6const ProductWidget = () => {7  return (8    <Container>9      <Header10        title="Items"11        actions={[12          {13            type: "custom",14            children: <CreateForm />,15          },16        ]}17      />18    </Container>19  )20}21
22export const config = defineWidgetConfig({23  zone: "product.details",24})25
26export default ProductWidget

This component uses the Container and Header custom components.

It will add at the top of a product's details page a new section, and in its header you'll find a Create button. If you click on it, it will open the focus modal with your form.


Edit Form#

In this section, you'll build a form component to edit an item of a resource.

Full Component

Unlike other components in this documentation, this form component isn't reusable. You have to create one for every resource that has an edit form in the admin.

Start by creating the file src/admin/components/edit-form.tsx in which you'll create the form.

Create Validation Schema#

In src/admin/components/edit-form.tsx, create a validation schema with Zod for the form's fields:

src/admin/components/edit-form.tsx
1import * as zod from "zod"2
3const schema = zod.object({4  name: zod.string(),5})

The form in this guide is simple, it only has a required name field, which is a string.

Initialize Form#

Next, you'll initialize the form using react-hook-form.

Add to src/admin/components/edit-form.tsx the following:

src/admin/components/edit-form.tsx
1// other imports...2import { useForm } from "react-hook-form"3
4// validation schema...5
6export const EditForm = () => {7  const form = useForm<zod.infer<typeof schema>>({8    defaultValues: {9      name: "",10    },11  })12
13  const handleSubmit = form.handleSubmit(({ name }) => {14    // TODO: submit to backend15    console.log(name)16  })17
18  // TODO render form19}

You create the EditForm component. For now, it uses useForm from react-hook-form to initialize a form.

You also define a handleSubmit function to perform an action when the form is submitted.

You can replace the content of the function with sending a request to Medusa's routes. Refer to this guide for more details on how to do that.

Render Components#

You'll now add a return statement that renders the drawer where the form is shown.

Replace // TODO render form with the following:

src/admin/components/edit-form.tsx
1// other imports...2import { 3  Drawer,4  Heading,5  Input,6  Button,7} from "@medusajs/ui"8import { Form } from "@medusajs/dashboard/components"9
10export const EditForm = () => {11  // ...12
13  return (14    <Drawer>15      <Drawer.Trigger asChild>16        <Button>Edit Item</Button>17      </Drawer.Trigger>18      <Drawer.Content>19        <Form {...form}>20          <form21            onSubmit={handleSubmit}22            className="flex flex-1 flex-col overflow-hidden"23          >24            <Drawer.Header>25              <Heading className="capitalize">26                Edit Item27              </Heading>28            </Drawer.Header>29            <Drawer.Body className="flex max-w-full flex-1 flex-col gap-y-8 overflow-y-auto">30              <Form.Field31                control={form.control}32                name="name"33                render={({ field }) => (34                  <Form.Item>35                    <Form.Label>Name</Form.Label>36                    <Form.Control>37                      <Input {...field} />38                    </Form.Control>39                    <Form.ErrorMessage />40                  </Form.Item>41                )}42              />43            </Drawer.Body>44            <Drawer.Footer>45              <div className="flex items-center justify-end gap-x-2">46                <Drawer.Close asChild>47                  <Button size="small" variant="secondary">48                    Cancel49                  </Button>50                </Drawer.Close>51                <Button size="small" type="submit">52                  Save53                </Button>54              </div>55            </Drawer.Footer>56          </form>57        </Form>58      </Drawer.Content>59    </Drawer>60  )61}

You render a drawer, with a trigger button to open it.

In the Drawer.Content component, you wrap the content with the Form component, spreading into it the form you initialized earlier.

In the Form component, you add a form element passing it the handleSubmit function you created earlier as the handler of the onSubmit event.

You render the form's fields inside the Drawer.Body. Each field is a Form.Field component whose render function returns a Form.Item component holding the field's Form.Label, Form.Control, and Form.ErrorMessage components.

Finally, in the Drawer.Footer component, you add buttons to save or cancel the form submission.

Use Edit Form Component#

You can use the EditForm component in your widget or UI route.

For example, create the widget src/admin/widgets/product-widget.tsx with the following content:

src/admin/widgets/product-widget.tsx
1import { defineWidgetConfig } from "@medusajs/admin-sdk"2import { Container } from "../components/container"3import { Header } from "../components/header"4import { EditForm } from "../components/edit-form"5
6const ProductWidget = () => {7  return (8    <Container>9      <Header10        title="Items"11        actions={[12          {13            type: "custom",14            children: <EditForm />,15          },16        ]}17      />18    </Container>19  )20}21
22export const config = defineWidgetConfig({23  zone: "product.details",24})25
26export default ProductWidget

This component uses the Container and Header custom components.

It will add at the top of a product's details page a new section, and in its header you'll find an "Edit Item" button. If you click on it, it will open the drawer with your form.

Was this page helpful?
Ask Bloom
For assistance in your development, use Claude Code Plugins or Docs MCP server in Cursor, VSCode, etc...FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break
⇧↵