{"pageProps":{"error":null,"preview":false,"file":{"fileRelativePath":"content/docs/plugins/forms.md","data":{"frontmatter":{"title":"Forms","prev":"/docs/cms","next":"/docs/plugins/fields","consumes":[{"file":"/packages/@tinacms/react-core/src/use-form.ts","description":"Describes the useForm hooks"},{"file":"/packages/@tinacms/forms/src/form.ts","description":"Form configuration"}]},"excerpt":" Forms in Tina are the main building blocks of your CMS. You will use Forms to: Arrange the editing interface for your content Expose your content to mutation through user edits Process and persist…","markdownBody":"\n**Forms** in Tina are the main building blocks of your CMS. You will use Forms to:\n\n- Arrange the editing interface for your content\n- Expose your content to mutation through user edits\n- Process and persist the changes to your content\n\n> **Use Form Helpers to Get Started Faster**\n>\n> This document explains how to set up forms in any React project. If you're using Gatsby or Next.js, we have helper packages that streamline this process for specific workflows:\n>\n> - [Editing Markdown Files with Gatsby](/guides/gatsby/git/create-remark-form)\n> - [Editing JSON Files with Gatsby](/guides/gatsby/git/create-json-form)\n> - [Editing local Markdown Files via Git with Next.js](/guides/nextjs/git/using-markdown)\n> - [Editing local JSON Files via Git with Next.js](/guides/nextjs/git/creating-git-forms)\n\nThe recommended way to create forms with Tina is to use the form hooks. These are explained in detail later on in this document, but let's start with a high-level overview of how form hooks are used.\n\nWhen using form hooks, they should be called inside a **Page** component; that is, the component that takes your content and renders a page from it. In the following contrived example, we have a Page component that receives its content in the component's props, including a `title` and some `markdownContent`:\n\n```javascript\nimport * as React from React\nimport ReactMarkdown from 'react-markdown'\n\nexport function Page(props) {\n return (\n
\n

{props.title}

\n \n
\n )\n}\n```\n\nHere's how we might call `useForm` to create a form that will be used to edit this content:\n\n```javascript\nimport * as React from React\nimport ReactMarkdown from 'react-markdown'\nimport { useForm, usePlugin } from 'tinacms'\n\nexport function Page(props) {\n // 1. Create the form\n const [modifiedValues, form] = useForm(formConfig) // formConfig object omitted for brevity; we'll get to this later\n\n // 2. Register it with the CMS\n usePlugin(form)\n\n return (\n
\n

{modifiedValues.title}

\n \n
\n )\n}\n\n```\n\n`useForm` returns an object containing all of the form's values that will change as the content is updated in the form. By switching out our original `props` in the rendering code for this new object, our page will re-render as the content is changed, giving us a real-time preview of the content!\n\n## Creating Forms\n\nThe `useForm` hook let's you create a form, but it does not [register it](/docs/plugins/forms#registering-forms) with the CMS.\n\nHere is how that hook works:\n\n```javascript\nconst [modifiedValues, form] = useForm(formConfig, watchedVars)\n```\n\n### Hook Return Values\n\nLike other React hooks, the form hooks enclose their return data in an array, expecting developers to assign these values via destructuring.\n\nThe first piece of data returned (`modifiedValues` in the above example) is an object containing all the data that is made editable by the form. As users edit data in the form, the values in this object change.\n\nThe second piece of data (`form` in the above example) is an form object that the hook created.\n\n### Form Configuration\n\nThe first argument that `useForm` receives (`formConfig` in the above example) is the object used to configure the form. Forms in Tina are built upon the [Final Form](https://final-form.org/) library, and inherit all of Final Form's configuration options.\n\nYou can see the all of Final Form's form config options in the [Form Config Documentation](https://final-form.org/docs/final-form/types/Config), but the following options will most commonly be used when creating a form:\n\n| key | description |\n| --------------- | --------------------------------------------------- |\n| `initialValues` | An object containing the initial state of the form. |\n| `onSubmit` | A function that runs when the form is saved. |\n\nIn addition to Final Form's options, Tina's form hooks accept the following additional configuration options:\n\n```typescript\ninterface FormOptions {\n id: any\n label: string\n fields: Field[]\n loadInitialValues?: () => Promise\n onSubmit?: () => Promise\n reset?(): void\n onChange?(state): void\n actions?: any[]\n buttons?: {\n save: string\n reset: string\n }\n __type?: string\n}\n```\n\n| key | description |\n| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id` | A unique identifier for the form. This should be derived from the content to distinguish it from other instances of the form. |\n| `label` | A label for the form that will appear in the sidebar. |\n| `fields` | An array of fields that will define the shape of the form and how content is edited. |\n| `loadInitialValues` | _Optional:_ A function to load the initial form state asynchronously. Return a promise that passes an object of form values when it resolves. |\n| `onSubmit` | _Optional:_ An asynchronous function to invoke when the form is saved, i.e. when the 'Save' button is pressed. |\n| `reset` | _Optional:_ A function that runs when the form state is reset by the user via the 'Reset' button. |\n| `actions` | _Optional:_ An array of custom actions that will be added to the form. |\n| `buttons` | _Optional:_ An object to customize the 'Save' and 'Reset' button text for the form. |\n| `onChange` | _Optional:_ A function that runs when the form values are changed. |\n| `__type` | _Optional:_ Sets the Form's plugin type. Automatically set based on which form hook is used. |\n\nNow that we know how to configure a form, let's revisit the simplified example from the beginning of this document to demonstrate how we might configure this form:\n\n```javascript\nimport * as React from React\nimport ReactMarkdown from 'react-markdown'\nimport { useForm, usePlugin } from 'tinacms'\n\nexport function Page(props) {\n const formConfig = {\n id: props.fileRelativePath,\n label: 'Edit Post',\n fields: [\n {\n name: 'title',\n label: 'Title',\n component: 'text',\n },\n {\n name: 'markdownContent',\n label: 'content',\n component: 'markdown',\n }\n ],\n initialValues: {\n title: props.title,\n markdownContent: props.markdownContent\n },\n onSubmit: async (formData) => {\n // save the new form data\n },\n }\n const [modifiedValues] = useForm(formConfig)\n\n usePlugin(form)\n\n return (\n
\n

{modifiedValues.title}

\n \n
\n )\n}\n\n```\n\n> Note that when using these basic hooks, you are expected to implement the save functionality yourself by adding an `onSubmit` function. By default, Tina makes no assumptions about how your content is stored. These basic form hooks are building blocks for creating more purpose-built tools to fit specific use cases.\n\n### Watched Vars: Preserve Form Reactivity\n\nThe second argument that can be passed to the form hooks is an optional object containing values that the form will need to react to. Use this when dealing with data from external sources or in an environment with Hot Module Replacement; in other words, when you expect this data to change and it's essential to keep the form in sync with it.\n\n```typescript\ninterface WatchableFormValue {\n values: object\n label: string\n fields: Field[]\n}\n```\n\n| key | description |\n| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `values` | Form will update its values when this data changes, but will avoid updating a field if it has UI focus. This is useful for keeping values in sync with a remote content source. |\n| `fields` | By watching the form's fields, they can be added/removed dynamically. |\n| `label` | When the form's label is derived from a dynamic value, this will ensure it is updated correctly. |\n\n## Registering Forms\n\nIn order to use a form you must register it with the CMS. There are two main approaches to register forms in Tina: page forms and screen plugins.\n\n### Example 1: Page Forms\n\nFor general page forms, use the `usePlugin` hook.\n\n> Tip: At one point these were known as _Local Forms_\n\n**src/templates/blog-post.js**\n\n```jsx\nimport { usePlugin } from 'tinacms'\nimport { useJsonForm } from 'gatsby-tinacms-json'\n\nfunction BlogPostTemplate(props) {\n // Create the form\n const [data, form] = useJsonForm(props.data.dataJson)\n\n // Register it with the CMS\n usePlugin(form)\n\n return

{data.firstName}

\n}\n```\n\n### Example 2: Forms as Screens\n\n[Screens](/docs/plugins/screens) are additional UI modals accessible from the CMS menu. Reference the [`useFormScreenPlugin` hook](/docs/plugins/screens#useformscreenplugin) to see an example of registering a form as a screen.\n\n## Form Helpers\n\nThe three hooks described thus far are the basic interface for creating forms, and they aim to support a broad set of use cases. For specific use cases, we've created some simplified interfaces for quickly setting up forms depending on the backend. Take a look at the integration packages for [Next.js](/docs/integrations/nextjs#packages) and [Gatsby](/docs/integrations/gatsby#packages) to learn more.\n\n## Customizing Form Buttons\n\n> _Note:_ The 'Save' and 'Reset' button text can also be configured on the sidebar or toolbar when defining the [CMS options](/docs/cms#cms-configuration). If `buttons` are configured on the CMS through the `sidebar` or `toolbar` options, those values will take precedent over button values passed to a form. **It is now recommended to configure button text on the form** intead of the CMS.\n\n'Save' & 'Reset' buttons accompany all forms. They may render at the bottom of the sidebar, in a modal, or on the toolbar. When clicked, these buttons should generally invoke the `onSubmit`(For 'Save') & `reset`(For 'Reset') functions associated with the form.\n\nThere may be times when you want to change the button text to improve UX or provide a **better description of the action** for an editor. For example, you may want the 'Save' button to say 'Commit' and the 'Reset' button to say 'Discard Changes'.\n\nBelow is an example of a form options object with these custom button options passed in:\n\n```js\nconst [author, authorForm] = useJsonForm(data.dataJson, {\n label: 'Author',\n fields: [\n {\n name: 'author',\n label: 'Author',\n component: 'select',\n options: [\n 'Herbert Huncke',\n 'Allen Ginsberg',\n 'Jack Kerouac',\n 'William S. Burroughs',\n ],\n },\n ],\n buttons: {\n save: 'Commit',\n reset: 'Discard Changes',\n },\n})\n```\n\n### Reuse with a composition\n\nWhen using the same button configuraiton for every form, it can be repetitive to continually define the button options. You can use a composition for this. Below is an example if you created a `cms` directory with a `useForm` composition to add the custom button values on every form.\n\n**src/cms/use-form.ts**\n\n```ts\nimport {\n useForm as baseUseForm,\n FormOptions,\n WatchableFormValues,\n} from 'tinacms'\nexport const useForm = (options: FormOptions, watch?: WatchableFormValues) =>\n baseUseForm(\n {\n buttons: {\n save: 'Save Changes',\n reset: 'Reset Form',\n },\n ...options,\n },\n watch\n )\n```\n\nThis is how that `useForm` composition could be used, setting up a baseline configuration for the general `useForm` hook from Tina.\n\n**src/pages/index.js**\n\n```ts\nimport { useForm } from '../cms/use-form'\n\nexport default function Home(props) {\n const formOptions = {\n //...\n }\n useForm(formOptions)\n}\n```\n\nThe `formOptions` defined in this example would be the [config object](/docs/cms#cms-configuration) referenced above.\n\n## Inline Forms\n\nRefer to the [Inline Editing](/docs/ui/inline-editing) docs.\n"}},"tocItems":"- [Creating Forms](#creating-forms)\n * [Hook Return Values](#hook-return-values)\n * [Form Configuration](#form-configuration)\n * [Watched Vars: Preserve Form Reactivity](#watched-vars-preserve-form-reactivity)\n- [Registering Forms](#registering-forms)\n * [Example 1: Page Forms](#example-1-page-forms)\n * [Example 2: Forms as Screens](#example-2-forms-as-screens)\n- [Form Helpers](#form-helpers)\n- [Customizing Form Buttons](#customizing-form-buttons)\n * [Reuse with a composition](#reuse-with-a-composition)\n- [Inline Forms](#inline-forms)","docsNav":[{"title":"Getting Started","id":"getting-started","items":[{"id":"/docs/getting-started/introduction","slug":"/docs/getting-started/introduction","title":"Introduction"}]},{"title":"CMS","id":"the-cms","items":[{"id":"/docs/cms","slug":"/docs/cms","title":"Overview"}]},{"title":"User Interface","id":"user-interface","items":[{"id":"/docs/ui","title":"Sidebar & Toolbar","slug":"/docs/ui"},{"title":"Inline Editing","id":"inline-editing","slug":"/docs/ui/inline-editing","items":[{"id":"/docs/ui/inline-editing/inline-text","title":"Inline Text","slug":"/docs/ui/inline-editing/inline-text"},{"id":"/docs/ui/inline-editing/inline-textarea","title":"Inline Textarea","slug":"/docs/ui/inline-editing/inline-textarea"},{"id":"/docs/ui/inline-editing/inline-wysiwyg","title":"Inline Wysiwyg","slug":"/docs/ui/inline-editing/inline-wysiwyg"},{"id":"/docs/ui/inline-editing/inline-image","title":"Inline Image","slug":"/docs/ui/inline-editing/inline-image"},{"id":"/docs/ui/inline-editing/inline-group","title":"Inline Group","slug":"/docs/ui/inline-editing/inline-group"},{"id":"/docs/ui/inline-editing/inline-blocks","title":"Inline Blocks","slug":"/docs/ui/inline-editing/inline-blocks"}]},{"id":"/docs/ui/alerts","title":"Alerts","slug":"/docs/ui/alerts"},{"id":"/docs/ui/styles","title":"Custom Styles","slug":"/docs/ui/styles"}]},{"id":"plugins","title":"Plugins","items":[{"title":"About Plugins","id":"forms","slug":"/docs/plugins"},{"title":"Forms","id":"forms","slug":"/docs/plugins/forms"},{"title":"Fields","id":"fields","slug":"/docs/plugins/fields","items":[{"id":"/docs/plugins/fields/text","slug":"/docs/plugins/fields/text","title":"Text"},{"id":"/docs/plugins/fields/textarea","slug":"/docs/plugins/fields/textarea","title":"Text Area"},{"id":"/docs/plugins/fields/number","slug":"/docs/plugins/fields/number","title":"Number"},{"id":"/docs/plugins/fields/image","slug":"/docs/plugins/fields/image","title":"Image"},{"id":"/docs/plugins/fields/color","slug":"/docs/plugins/fields/color","title":"Color"},{"id":"/docs/plugins/fields/toggle","slug":"/docs/plugins/fields/toggle","title":"Toggle"},{"id":"/docs/plugins/fields/select","slug":"/docs/plugins/fields/select","title":"Select"},{"id":"/docs/plugins/fields/tags","slug":"/docs/plugins/fields/tags","title":"Tags"},{"id":"/docs/plugins/fields/list","slug":"/docs/plugins/fields/list","title":"List"},{"id":"/docs/plugins/fields/group","slug":"/docs/plugins/fields/group","title":"Group"},{"id":"/docs/plugins/fields/group-list","slug":"/docs/plugins/fields/group-list","title":"Group List"},{"id":"/docs/plugins/fields/blocks","slug":"/docs/plugins/fields/blocks","title":"Blocks"},{"id":"/docs/plugins/fields/date","slug":"/docs/plugins/fields/date","title":"Date & Time"},{"id":"/docs/plugins/fields/markdown","slug":"/docs/plugins/fields/markdown","title":"Markdown"},{"id":"/docs/plugins/fields/html","slug":"/docs/plugins/fields/html","title":"HTML"},{"id":"/docs/plugins/fields/custom-fields","slug":"/docs/plugins/fields/custom-fields","title":"Custom Fields"}]},{"title":"Content Creators","id":"content-creator","slug":"/docs/plugins/content-creators"},{"title":"Screens","id":"screens","slug":"/docs/plugins/screens"},{"title":"Toolbar Widgets","id":"toolbar:widget","slug":"/docs/plugins/toolbar-widgets"}]},{"id":"events","title":"Events","items":[{"id":"/docs/events","slug":"/docs/events","title":"About Events"}]},{"title":"Media","id":"media","items":[{"id":"/docs/media","slug":"/docs/media","title":"About Media"}]},{"id":"apis","title":"External APIs","items":[{"id":"/docs/api","slug":"/docs/apis","title":"About APIs"}]},{"title":"Integrations","id":"nextjs","items":[{"id":"/docs/integrations/nextjs","slug":"/docs/integrations/nextjs","title":"Next.js"},{"id":"/docs/integrations/gatsby","slug":"/docs/integrations/gatsby","title":"Gatsby"}]},{"title":"Release Notes","id":"releases","items":[{"id":"/docs/releases","href":"/docs/releases","title":"All Releases"}]},{"title":"Packages","id":"packages","items":[{"id":"/packages/react-tinacms-date","slug":"/packages/react-tinacms-date","title":"react-tinacms-date"},{"id":"/packages/react-tinacms-editor","slug":"/packages/react-tinacms-editor","title":"react-tinacms-editor"},{"id":"/packages/react-tinacms-github","slug":"/packages/react-tinacms-github","title":"react-tinacms-github"},{"id":"/packages/react-tinacms-inline","slug":"/packages/react-tinacms-inline","title":"react-tinacms-inline"},{"id":"/packages/react-tinacms-strapi","slug":"/packages/react-tinacms-strapi","title":"react-tinacms-strapi"},{"id":"/packages/next-tinacms-github","slug":"/packages/next-tinacms-github","title":"next-tinacms-github"},{"id":"/packages/next-tinacms-json","slug":"/packages/next-tinacms-json","title":"next-tinacms-json"},{"id":"/packages/next-tinacms-markdown","slug":"/packages/next-tinacms-markdown","title":"next-tinacms-markdown"},{"id":"/packages/gatsby-plugin-tinacms","slug":"/packages/gatsby-plugin-tinacms","title":"gatsby-plugin-tinacms"},{"id":"/packages/gatsby-tinacms-git","slug":"/packages/gatsby-tinacms-git","title":"gatsby-tinacms-git"},{"id":"/packages/gatsby-tinacms-json","slug":"/packages/gatsby-tinacms-json","title":"gatsby-tinacms-json"},{"id":"/packages/gatsby-tinacms-remark","slug":"/packages/gatsby-tinacms-remark","title":"gatsby-tinacms-remark"}]}],"nextPage":{"slug":"/docs/plugins/fields","title":"Field Plugins"},"prevPage":{"slug":"/docs/cms","title":"The CMS"}},"__N_SSG":true}