Forms - Admin Components

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.

Form Tooling#

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

  1. react-hook-form to easily build forms and manage their states.
  2. Zod to validate the form's fields.

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


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 that you'll create the form in.

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  Label,6  Input,7  Button,8} from "@medusajs/ui"9import { 10  FormProvider,11  Controller,12} from "react-hook-form"13
14export const CreateForm = () => {15  // ...16
17  return (18    <FocusModal>19      <FocusModal.Trigger asChild>20        <Button>Create</Button>21      </FocusModal.Trigger>22      <FocusModal.Content>23        <FormProvider {...form}>24          <form25            onSubmit={handleSubmit}26            className="flex h-full flex-col overflow-hidden"27          >28            <FocusModal.Header>29              <div className="flex items-center justify-end gap-x-2">30                  <FocusModal.Close asChild>31                    <Button size="small" variant="secondary">32                      Cancel33                    </Button>34                  </FocusModal.Close>35                  <Button type="submit" size="small">36                    Save37                  </Button>38                </div>39            </FocusModal.Header>40            <FocusModal.Body>41                <div className="flex flex-1 flex-col items-center overflow-y-auto">42                  <div className="mx-auto flex w-full max-w-[720px] flex-col gap-y-8 px-2 py-16">43                    <div>44                      <Heading className="capitalize">45                        Create Item46                      </Heading>47                    </div>48                    <div className="grid grid-cols-2 gap-4">49                      <Controller50                        control={form.control}51                        name="name"52                        render={({ field }) => {53                          return (54                            <div className="flex flex-col space-y-2">55                              <div className="flex items-center gap-x-1">56                                <Label size="small" weight="plus">57                                  Name58                                </Label>59                              </div>60                              <Input {...field} />61                            </div>62                          )63                        }}64                      />65                    </div>66                  </div>67                </div>68            </FocusModal.Body>69          </form>70        </FormProvider>71      </FocusModal.Content>72    </FocusModal>73  )74}

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

In the FocusModal.Content component, you wrap the content with the FormProvider component from react-hook-form, passing it the details of the form you initialized earlier as props.

In the FormProvider, you add a form component 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 components inside the FocusModal.Body. To render inputs, you use the Controller component imported from react-hook-form.

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.before",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 that you'll create the form in.

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  Label,6  Input,7  Button,8} from "@medusajs/ui"9import { 10  FormProvider,11  Controller,12} from "react-hook-form"13
14export const EditForm = () => {15  // ...16
17  return (18    <Drawer>19      <Drawer.Trigger asChild>20        <Button>Edit Item</Button>21      </Drawer.Trigger>22      <Drawer.Content>23        <FormProvider {...form}>24          <form25            onSubmit={handleSubmit}26            className="flex flex-1 flex-col overflow-hidden"27          >28          <Drawer.Header>29            <Heading className="capitalize">30              Edit Item31            </Heading>32          </Drawer.Header>33          <Drawer.Body className="flex max-w-full flex-1 flex-col gap-y-8 overflow-y-auto">34            <Controller35              control={form.control}36              name="name"37              render={({ field }) => {38                return (39                  <div className="flex flex-col space-y-2">40                    <div className="flex items-center gap-x-1">41                      <Label size="small" weight="plus">42                        Name43                      </Label>44                    </div>45                    <Input {...field} />46                  </div>47                )48              }}49            />50          </Drawer.Body>51          <Drawer.Footer>52            <div className="flex items-center justify-end gap-x-2">53              <Drawer.Close asChild>54                <Button size="small" variant="secondary">55                  Cancel56                </Button>57              </Drawer.Close>58              <Button size="small" type="submit">59                Save60              </Button>61            </div>62          </Drawer.Footer>63          </form>64        </FormProvider>65      </Drawer.Content>66    </Drawer>67  )68}

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

In the Drawer.Content component, you wrap the content with the FormProvider component from react-hook-form, passing it the details of the form you initialized earlier as props.

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

You render the form's components inside the Drawer.Body. To render inputs, you use the Controller component imported from react-hook-form.

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.before",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?
Edit this page
Ask Anything
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