Create a “coupon generator” string field input

Take the guesswork out of creating fields with correct values and automate content creation for authors.

Take the guesswork out of creating fields with correct values and automate content creation for authors.

What you need to know

This guide assumes that you know how to set up and configure Sanity Studio and have basic knowledge about defining a schema with document and field types. Basic knowledge of React and TypeScript is also useful, although you should be able to copy-paste the example code to get a runnable result.

The code examples import Code from @sanity/ui/code, which requires @sanity/ui 4.0.0 or later. Sanity Studio v6.9.2 and later include a compatible version.

Custom form components by example

One of Sanity Studio’s most powerful features is custom drop-in replacements for form fields. This guide is one in a series of code examples.

You can get more familiar with the Form Components API in the documentation.

What you’ll be making

A string field that can generate its own valid strings:

Create the schema types

In this example, you’ll build a custom coupon generator. In this instance, a coupon is a string field that is four characters long, containing only uppercase letters and numbers.

The simplest way to add this sort of input to a document in Sanity would be a string field with a validation rule. This is functional but not pleasant to your authors. With a custom input they can generate these codes in one click while still having full control over the editing input.

Create a new field in your studio, and register it to your schema in sanity.config.ts:

// ./schema/coupon/couponType.ts

import {defineType} from 'sanity'

export const couponType = defineType({
  name: 'coupon',
  title: 'Coupon',
  description: 'A unique, all uppercase, four-character alphanumeric code',
  type: 'string',
  validation: (rule) =>
    rule
      .min(4)
      .max(4)
      .regex(/^[A-Z0-9]+$/),
})

Creating a new schema type for this string lets you reuse it throughout your studio. For example, multiple document types can use this coupon field type with its custom input, but with unique options. After you import this schema type into your studio’s schema, you can refer to this type by its name value: type: 'coupon'.

Add the coupon field type to a document type’s fields:

// ./schema/storeType.ts

import {defineField, defineType} from 'sanity'

export const storeType = defineType({
  name: 'store',
  title: 'Store',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      type: 'string',
    }),
    defineField({
      name: 'coupon',
      type: 'coupon',
    }),
  ],
})

Register both schema types in the schema.types array of your sanity.config.ts file:

// ./sanity.config.ts

import {defineConfig} from 'sanity'
import {couponType} from './schema/coupon/couponType'
import {storeType} from './schema/storeType'

export default defineConfig({
  // ...all other settings
  schema: {
    types: [couponType, storeType],
  },
})

In your studio, create a new store document. The title and coupon fields both render as default string inputs:

Two string fields with default functionality

Create an input component

Create a new component in your studio with the following code:

// ./schema/coupon/CouponInput.tsx

import {Box, Button, Flex} from '@sanity/ui'
import {StringInputProps} from 'sanity'

export function CouponInput(props: StringInputProps) {
  return (
    <Flex gap={3} align="center">
      <Box flex={1}>{props.renderDefault(props)}</Box>
      <Button mode="ghost" text="Generate coupon" />
    </Flex>
  )
}

Notice the following:

  • On line 3, the imports from Sanity UI help you create custom components that use the same UI and design language as the rest of the studio. For more information, see Get started with Sanity UI.
  • On line 9, props.renderDefault(props) renders the original string input, so you don’t need to handle complex APIs like validation and presence yourself.

To use this component, you’ll need to load it into the correct slot back on the coupon schema. You’ll use input here because you don’t want to replace the field’s title and description.

// ./schema/coupon/couponType.ts

import {defineType} from 'sanity'
import {CouponInput} from './CouponInput'

export const couponType = defineType({
  name: 'coupon',
  // ...all other settings
  components: {input: CouponInput},
})

Open your store document again. The coupon field now shows your custom input and a Generate coupon button, but the button doesn’t do anything yet.

The coupon string field now has a custom button

To make the button write a value to the field, use the onChange function from the input’s props.

Handle changes and patch data

Custom inputs contain helpful functions and details in their props. This input only needs one: onChange.

This function wraps any patch (such as setting or unsetting the value of a field) and keeps the rest of the studio up to date with changes.

Update your CouponInput component to use the following code:

// ./schema/coupon/CouponInput.tsx

import {Box, Button, Flex} from '@sanity/ui'
import {Code} from '@sanity/ui/code'
import {set, StringInputProps} from 'sanity'
import {useCallback} from 'react'

export function CouponInput(props: StringInputProps) {
  // onChange handles patches to the document
  const {onChange} = props

  const generateCoupon = useCallback(() => {
    const coupon = Math.random().toString(36).substring(2, 6).toUpperCase()
    // "set()" will write a value to this field
    onChange(set(coupon))
  }, [onChange])

  return (
    <Flex gap={3} align="center">
      <Box flex={1}>{props.renderDefault(props)}</Box>
      {/* Display the value in a monospaced font */}
      {props.value ? <Code size={4}>{props.value}</Code> : null}
      <Button mode="ghost" onClick={generateCoupon} text="Generate coupon" />
    </Flex>
  )
}
  • Notice how onChange is destructured from the component’s props.
  • The onChange function is then used inside generateCoupon, with the set() function, to update the field’s value. The new value is validated immediately and updated in the browser of any other authors viewing the same document.
  • The generateCoupon function is registered with a useCallback hook to cache it between re-renders.
  • Sometimes these codes can contain easily confused characters (such as 0 and O), so rendering the current field’s value in the <Code> component can make them clearer.

Now you have a fully functional, automated, and editable coupon generator field with a handy visual preview!

The button now writes to the string field along with a rich visual preview

Next steps

Some ideas to extend this custom input include:

  • Extend the coupon field’s validation rule to use rule.custom() and check that no other document in the dataset contains the same coupon code.
  • Add configurability to the coupon field schema, such as a setting in options to determine the length of the generated coupon string.
  • Change the generateCoupon function and the field validation so that they don’t create or allow potentially confusing characters such as 0 and O.
  • Import unset from sanity and add an extra button to remove the coupon from the field.

Was this page helpful?

sanity.ioAll Systems Operational© Sanity 2026
Change site theme