Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Creating custom form field payload 3.0, using the form builder plugin

default discord avatar
andersjacobsonlast year
8

Hi everybody, I am new to payload and I am trying to create a custom form field for a phone number. I am using the website template and I am trying to create a custom form field within the form builder, if that is possible. I have gotten stuck.


I have:


created a component based of the textfield type with validation, similar to the default form fields


added it to the fields file that exports it to the plugins and then into payload-config


tried adding it to the form builder plugin as true, which didn't work and have also tested variations of formoverrides in the formbuilderplugin code. Has anyone solved this?



thanks, sorry for any newbie things in this post 🙂



The form file is inspired by the existing forms


export const Phone: React.FC<


TextField & {


errors: Partial<FieldErrorsImpl>


register: UseFormRegister<FieldValues>


}


= ({ name, defaultValue, errors, label, register, required, width }) => { return ( <width...> etc)}

imported it into my fields.ts file:
import { Phone } from './Phone'
export const fields = {
phone: Phone,
// ... existing code
}

And tested a bunch of stuff in the plugins file (and tried to generate to payload-types file):
formBuilderPlugin({
fields: {
payment: false,
phone: true,
},

part from the above I have tried a few variations of the formoverides and the submissionoverrides, but havent managed to get it to work, found here: https://payloadcms.com/docs/plugins/form-builder



>

  • default discord avatar
    tomkat_last year

    I'm dealing with the exact same issue as we speak, have created additional form blocks for phone number, and mobile phone number, can't seem to work out how to pass them to the form builder plug in config correctly. Followed the same approach as you have. Going to keep working on it today, will update here when I work it out. 🙂



    I solved this. I added a path in the form blocks directory for phoneNumber, in this directory i have a block.ts file, and an index.tsx file.



    The block.ts file initiates the component in the admin panel, I had to create the required fields here, and then pass these fields in the block object structure. If you use the Field type from payload for each Field, and then the Block type from payload for your Block component, you shouldn't need to create any extended types etc in your client component that renders the block.



    This is my index.ts file here which looks after the tsx, and renders our PhoneNumber block. I have some additional validation here, but you can configure this to your specific requirements (mines localised for Australian mobile / cell numbers currently).



    Then in my form builder plug in config, I simply just pass in the newly created form field into the fields object.



    I hope this works for you.



    import { Field, Block } from "payload";
    
    export const name: Field = {
      name: "name",
      type: "text",
      label: "Name (lowercase, no special characters please)",
      required: true,
    };
    
    export const label: Field = {
      name: "label",
      type: "text",
      label: "Label",
      localized: true,
    };
    
    export const required: Field = {
      name: "required",
      type: "checkbox",
      label: "Required",
      defaultValue: true,
    };
    
    export const width: Field = {
      name: "width",
      type: "number",
      label: "Field width (percentage)",
    };
    
    export const PhoneNumber: Block = {
      slug: "phoneNumber",
      fields: [
        {
          type: "row",
          fields: [
            {
              ...name,
              admin: {
                width: "50%",
              },
            },
            {
              ...label,
              admin: {
                width: "50%",
              },
            },
          ],
        },
        {
          type: "row",
          fields: [
            {
              ...width,
              admin: {
                width: "50%",
              },
            },
            {
              name: "defaultValue",
              type: "text",
              label: "Default Value",
              admin: {
                width: "50%",
              },
            },
          ],
        },
        required,
      ],
      labels: {
        singular: "Phone Number",
        plural: "Phone Numbers",
      },
    };


    import React from "react";
    import type { FieldErrorsImpl, FieldValues, UseFormRegister } from "react-hook-form";
    import type { TextField } from "@payloadcms/plugin-form-builder/types";
    import { Input } from "@/components/ui/input";
    import { Label } from "@/components/ui/label";
    
    import { Error } from "../Error";
    import { Width } from "../Width";
    
    export const PhoneNumber: React.FC<
      TextField & {
        placeholder?: string;
        errors: Partial<FieldErrorsImpl>;
        register: UseFormRegister<FieldValues>;
      }
    > = ({ name, defaultValue, errors, label, register, required, width, placeholder }) => {
      return (
        <Width width={width}>
          <Label className="block mb-1" htmlFor={name}>
            {label}
            {required && (
              <span className="required">
                * <span className="sr-only">(required)</span>
              </span>
            )}
          </Label>
          <Input
            defaultValue={defaultValue}
            placeholder={placeholder || "04XX XXX XXX or +61 X XXXX XXXX"}
            id={name}
            type="tel"
            {...register(name, {
              required,
              validate: (value) => {
                if (!value && !required) return true;
    
                const digitsOnly = value.replace(/\D/g, "");
                const isAusMobile = /^04\d{8}$/.test(digitsOnly);
                const isIntMobile = /^\+61\d{9}$/.test(digitsOnly);
    
                return isAusMobile || isIntMobile || "Please enter a valid mobile number";
              },
            })}
          />
          {errors[name] && <Error name={name} />}
        </Width>
      );
    };


    import { formBuilderPlugin } from "@payloadcms/plugin-form-builder";
    import { FixedToolbarFeature, HeadingFeature, lexicalEditor } from "@payloadcms/richtext-lexical";
    import { PhoneNumber } from "@/blocks/Form/PhoneNumber/block";
    
    export const formBuilderConfig = formBuilderPlugin({
      fields: {
        payment: false,
        phoneNumber: PhoneNumber,
      },
      formOverrides: {
        fields: ({ defaultFields }) => {
          return defaultFields.map((field) => {
            if ("name" in field && field.name === "confirmationMessage") {
              return {
                ...field,
                editor: lexicalEditor({
                  features: ({ rootFeatures }) => {
                    return [
                      ...rootFeatures,
                      FixedToolbarFeature(),
                      HeadingFeature({ enabledHeadingSizes: ["h1", "h2", "h3", "h4"] }),
                    ];
                  },
                }),
              };
            }
            return field;
          });
        },
      },
    });


    @213906816248053760
  • default discord avatar
    andersjacobsonlast year

    hey, great work, didn't come back to this post until today, had given up hope it would get a response. I'll see what I can learn from what you posted above and let you know. Appreciate it a lot, thanks.

  • default discord avatar
    tomkat_last year

    Hey

    @476408822538305538

    , no problem. It is definitely not overly clear in the docs etc. It would be nice if they included the admin panel block files in the website example, it would make it a bit clearer. I am not sure my approach is perfect, But seems to work well and function as expected in the app i'm working on. Good luck with your project.

  • default discord avatar
    andersjacobsonlast year

    hey

    @702287438206468200

    it worked, thanks. And it looks good too. Becoming a programmer feels daunting....good luck with your project too.

  • default discord avatar
    tomkat_last year

    Hey @Anders. Haha don't worry my friend, I know that feeling, the daunting feeling never goes away, I just learned to embrace it. Really happy you got it working ok. Feel free to mention me if you have any future issues, Not sure I'll always be able to help, but I'm always happy to take a look.

  • default discord avatar
    andersjacobsonlast year

    thanks

    @702287438206468200

    , appreciate it.

  • default discord avatar
    emot1onz.12 months ago

    thank you

    @702287438206468200

    that helped me too!

  • default discord avatar
    andersjacobson12 months ago

    here is also a good post that adds variation to tomkats solution:

    https://dev.to/mikecebul/payload-from-builder-plugin-custom-fields-5d3j
Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.