@RequestMapping("/console/app/(*:appId)/(~:version)/form/builder/(*:formId)")
  public String formBuilder(
      ModelMap model,
      @RequestParam("appId") String appId,
      @RequestParam(value = "version", required = false) String version,
      @RequestParam("formId") String formId,
      @RequestParam(required = false) String json) {
    // verify app version
    ConsoleWebPlugin consoleWebPlugin =
        (ConsoleWebPlugin) pluginManager.getPlugin(ConsoleWebPlugin.class.getName());
    String page = consoleWebPlugin.verifyAppVersion(appId, version);
    if (page != null) {
      return page;
    }

    // set flag in request
    FormUtil.setFormBuilderActive(true);

    // load form definition
    model.addAttribute("appId", appId);
    AppDefinition appDef = appService.getAppDefinition(appId, version);
    FormDefinition formDef = null;
    if (appDef == null) {
      // TODO: handle invalid app
    } else {
      model.addAttribute("appDefinition", appDef);
      formDef = formDefinitionDao.loadById(formId, appDef);
    }

    if (formDef != null) {
      String formJson = null;
      if (json != null && !json.trim().isEmpty()) {
        // read custom JSON from request
        formJson = json;
      } else {
        // get JSON from form definition
        formJson = formDef.getJson();
      }
      if (formJson != null && formJson.trim().length() > 0) {
        String processedformJson = PropertyUtil.propertiesJsonLoadProcessing(formJson);

        try {
          FormUtil.setProcessedFormJson(processedformJson);
          String elementHtml = formService.previewElement(formJson);
          model.addAttribute("elementHtml", elementHtml);
          model.addAttribute("elementJson", processedformJson);
        } finally {
          FormUtil.clearProcessedFormJson();
        }
      } else {
        // default empty form
        String tableName = formDef.getTableName();
        if (tableName == null || tableName.isEmpty()) {
          tableName = formDef.getId();
        }
        String escapedFormName = StringEscapeUtils.escapeJavaScript(formDef.getName());
        String defaultJson =
            "{className: 'org.joget.apps.form.model.Form',  \"properties\":{ \"id\":\""
                + formId
                + "\", \"name\":\""
                + escapedFormName
                + "\", \"tableName\":\""
                + tableName
                + "\", \"loadBinder\":{ \"className\":\"org.joget.apps.form.lib.WorkflowFormBinder\" }, \"storeBinder\":{ \"className\":\"org.joget.apps.form.lib.WorkflowFormBinder\" } }}";
        String formHtml = formService.previewElement(defaultJson);
        model.addAttribute("elementHtml", formHtml);
      }
    } else {
      // default empty form
      String formJson =
          "{className: 'org.joget.apps.form.model.Form',  \"properties\":{ \"id\":\""
              + formId
              + "\", \"name\":\"\" \"loadBinder\":{ \"className\":\"org.joget.apps.form.lib.WorkflowFormBinder\" }, \"storeBinder\":{ \"className\":\"org.joget.apps.form.lib.WorkflowFormBinder\" } }}";
      String formHtml = formService.previewElement(formJson);
      model.addAttribute("elementHtml", formHtml);
    }

    // add palette
    model.addAttribute("palette", formBuilderPalette);

    // add form def id
    model.addAttribute("formId", formId);
    model.addAttribute("formDef", formDef);

    return "fbuilder/formBuilder";
  }