Custom Fields & API Integration

You can attach an unlimited number of custom fields to individual HireHop records (Jobs, Projects, Assets, Service Tests, etc.). These custom fields seamlessly merge into document templates. If a custom field exists on a record, it populates automatically; otherwise, it resolves as blank.

Currently, custom fields are fully supported on Jobs and Projects using custom JavaScript plugins.

Custom Fields Data Structure

When fetching custom field values for an active record, call the _get_custom_field_value(field) function. It returns NULL if unset, a plain string, or a structured JavaScript object.

For maximum printing control in documents, store custom fields as a structured JSON object rather than a plain string:

"field_name" :
   {
      "value"  : "The value of the field",
      "type"   : "The field type, default is text, it can also be number, currency, text, date, html and array"
      "format" : "For date type only, eg "ddd, dddddd tt" // = "Mon, 1 January 2017 12:00"
   }

Object Keys & Supported Formats

ParameterTypeDescription
valueAnyThe raw field data stored in any valid JavaScript format.
typeStringTells HireHop how to render the data in documents. Supported types: text (default), number, currency, date, html, and array (rendered as JSON).
formatStringDate types only. Specifies date/time output formatting based on user locale.

Date Formatting Rules

Date values must be stored in yyyy-mm-dd hh:mm format. You can combine the following tokens and custom separators:

  • dddddd – Long date (e.g., 1 January 2026)
  • ddddd – Short date (e.g., 01/01/2026)
  • dddd – Full day of the week (e.g., Monday)
  • ddd – Short day of the week (e.g., Mon)
  • tt – Time (e.g., 12:36 am)

Example: dddd, dddddd tt outputs “Monday, January 1 2026 12:00”.

Saving Custom Fields

To store updated custom field values on supported edit forms, use _save_custom_field_value(field, value).

  • Batch Operations: All changes must be written prior to submitting the form save action. New values merge with existing fields; matching key names will overwrite existing values.
  • Direct API Calls: When calling endpoints like /php_functions.job_save.php directly, passing only the custom_fields POST parameter updates the custom fields while leaving all other record attributes untouched.

Naming & Printing Conventions

  • Document Merge Syntax: In document templates, prefix field names with a single underscore _. For example, a custom field named field_name on a Job merges as job:_field_name.
  • Namespace Prefixes: To prevent naming collisions across different plugins or record types (e.g., shared asset and test fields), prefix field names with a unique identifier (e.g., HireHop plugins use hh_Field Name).
  • Case Sensitivity: Document merge tags are case-insensitive, but JavaScript variable references are case-sensitive.

Searchable Custom Fields

The CUSTOM_INDEX field is a special 45-character string value (or NULL) reserved for custom searching, filtering, and home page search results.

Enabling Home Page Search

To include CUSTOM_INDEX in global search columns, append it to allSearchCols and set the label in your plugin:

// Add CUSTOM_INDEX to search columns
if (allSearchCols.constructor === Array && doc_type == 0) {
  allSearchCols.push("CUSTOM_INDEX");
}

// Define display label if not set in language settings
if (typeof(lang["customIndexTxt"]) == "undefined" || lang["customIndexTxt"] == "") {
  lang["customIndexTxt"] = "Custom Field Name";
}

Displaying & Interacting with CUSTOM_INDEX

On Job pages, unhide the tile element to hook custom interactions:

$("#job_tile_custom_index")
  .show()
  .click(function() {
    window.open("https://www.my_external_app.com?id=" + job_data["CUSTOM_INDEX"], "newwindow");
  });

Adding CUSTOM_INDEX to Edit Widgets

To allow users to input CUSTOM_INDEX values inside the Job Edit dialog, inject a form row into the widget:

if (typeof($.custom.job_edit) != "undefined") {
  $.widget("custom.job_edit", $.custom.job_edit, {
    _init_main: function() {
      this._super(arguments);
      
      // Inject custom field row into the edit table
      var table = this.default_disc.closest("table");
      var tr = $("<tr>").appendTo(table);
      
      $("<td>", { html: lang.customIndexTxt + " :" }).appendTo(tr);
      $("<input>", {
        "name": "custom_index",
        "class": "data_cell",
        "data-field": "CUSTOM_INDEX",
        "maxlength": 45
      }).appendTo($("<td>").appendTo(tr));
      
      // Adjust layout spacing
      this.job_edit_memo.height(110);
    }
  });
}

Note: In documents, reference this field as xxx:custom_index.

Global Custom Fields

To store company-wide global settings or counters, use the global custom fields endpoints (/php_functions/custom_fields_global_load.php and /php_functions/custom_fields_global_save.php):

$("#saving_dialog").dialog("open");

$.ajax({
  url: "/php_functions/custom_fields_global_save.php",
  type: "post",
  dataType: "json",
  data: {
    "fields": { "my_field": "any type of value" }
  },
  success: function(data) {
    $("#saving_dialog").dialog("close");
    if (typeof(data.error) !== "undefined") {
      error_message(isNaN(parseInt(data.error)) ? data.error : lang.error[data.error]);
    } else {
      // Returns JSON object containing all global custom fields
    }
  },
  error: function(jqXHR, textStatus, errorThrown) {
    $("#saving_dialog").dialog("close");
    error_message(lang.error[1] + " (" + errorThrown + ").");
  }
});

Settings vs. API Custom Fields

Fields created in Settings > Custom Fields share the exact same underlying database storage as API-injected custom fields. Setting up a field in the UI merely provides a codeless builder; deleting a UI-created custom field does not delete historical field data, which remains accessible via the API.