Try

Create Custom Form Rules and Behaviors with Content Type Form Controllers

Applicable to CrafterCMS 4.x
Photo of Russ Danner

Russ Danner

In CrafterCMS, there are many points in a content item’s lifecycle: create, update, delete, copy, paste, and duplicate, where you might want something special to happen automatically. That’s precisely what content form controllers are for.

Form controllers are scriptable lifecycle event handlers that let you run custom logic either:

  • On the client side (in the form on load, on save and other events)

  • On the server side (during content write/delete operations in the backend)

Why Use Form Controllers?

Form controllers are ideal for scenarios that require automation or custom validation across multiple fields, or when external information is needed before publishing content. Examples include:

  • Synthesizing new values from existing fields
    e.g., automatically generating a slug from a title.

  • Tying two fields together
    e.g., If one field changes, update another field dynamically.

  • Validating content
    e.g., Querying a remote system to verify a value before saving.

  • Blocking saves if the custom validation rule fails.

Simple Client-Side Example

Imagine you want to ensure the Internal Name always ends with a star (*):

  • You’d write a form-controller.js script for the relevant content type.

  • In the onBeforeSave() method, you’d check if the value ends with *; if not, append it.

  • This all happens before the save request is sent to the server.

The result? Users save their content normally, but your rule ensures the formatting is always correct.

CStudioForms.FormControllers.PageHomeController = CStudioForms.FormControllers.PageHomeController ||  function() {};

YAHOO.extend(CStudioForms.FormControllers.PageHomeController, CStudioForms.FormController, {

    onBeforeSave : function () {
        var internalName = this.form.model['internal-name'];
        if(!internalName.endsWith("*")) {
            this.form.model['internal-name'] =  internalName + '*'
        }
        
        return true
    }
});

CStudioAuthoring.Module.moduleLoaded("/page/entry-controller", CStudioForms.FormControllers.PageHomeController );

You can enable a client-side controller via a <controller>true</controller> tag in the type’s config XML by adding the form-controller.js

Note that the last line in the script informs the plugin system that the controller is loaded and associates the name of the content type (-controller) with the class.

Simple Server-Side Example

On the backend, a controller.groovy file lets you:

  • Inspect the content, user, path, type, and lifecycle operation (create/update/move/etc).

  • Run logic before the save completes, such as integration calls, data checks, or transformations. 

  • Block, modify, or enhance the save process without touching the UI.

A simple starter script might just log the lifecycle event type (create, update, etc.) to the server console, but the possibilities extend far beyond that.

import scripts.libs.CommonLifecycleApi

def contentLifecycleParams =[:]
contentLifecycleParams.site = site
contentLifecycleParams.path = path
contentLifecycleParams.user = user
contentLifecycleParams.contentType = contentType
contentLifecycleParams.contentLifecycleOperation = contentLifecycleOperation
contentLifecycleParams.contentLoader = contentLoader
contentLifecycleParams.applicationContext = applicationContext

def controller = new CommonLifecycleApi(contentLifecycleParams)
controller.execute()


System.out.println("Server side content lifecycle event : " + contentLifecycleOperation)

Server-side controllers are automatically wired in through their controller.groovy.

📺 Watch the Demo Video:
For a full walkthrough with live examples of both client-side and server-side controllers, check out our video here: Content Form Controllers in CrafterCMS.

Share this Post

Related Tags

Related Posts

Related Resources