Lifecycle Hooks

Cordra validates incoming information against schemas as defined in the Type objects. Additional rules that validate and/or enrich the information in the object can be configured to be applied by Cordra at various stages of the object lifecycle. Such rules are expected to be specified in JavaScript and to be bound to the following lifecycle points:

  • before an object is schema validated;
  • during id generation;
  • before an object is indexed, allowing the object that is indexed to differ from the one that is stored;
  • during handle record creation;
  • after an object (or payload) has been retrieved from storage, but before it is returned;
  • before an object is deleted, to forbid deletion under some circumstances;
  • after an object is deleted;
  • after an object is created or updated; and
  • before executing a user-supplied query.

Lifecycle hooks, in our parlance, are the points of entry for your own JavaScript-based rules that Cordra processes. In addition to the lifecycle hooks that are discussed in detail below, Cordra enables clients to invoke other rules in an ad hoc fashion using Type Methods.

Currently, various lifecycle hooks are enabled in Cordra for different actions: create, update, retrieve, and delete.

The following diagrams illustrate hooks that are enabled during various stages of the object lifecycle.

Create Lifecycle
Update Lifecycle
Retrieve Lifecycle
Delete Lifecycle

Using Hooks in JavaScript

Hooks in a Type Object

Most lifecycle hooks are available for use as part of the JavaScript associated with each Type object. This means if you want to leverage these hooks for multiple types of objects, then you will need to edit the JavaScript for each of the types. See Using External Modules for a method to share code among multiple types.

Here is the shell of the hooks that are available in each Type, which will be explained below.

 var cordra = require('cordra');
 var cordraUtil = require('cordraUtil');
 var schema = require('/cordra/schemas/Type.schema.json');
 var js = require('/cordra/schemas/Type');

 exports.beforeSchemaValidation = beforeSchemaValidation;
 exports.objectForIndexing = objectForIndexing;
 exports.onObjectResolution = onObjectResolution;
 exports.onPayloadResolution = onPayloadResolution;
 exports.beforeDelete = beforeDelete;
 exports.afterDelete = afterDelete;
 exports.afterCreateOrUpdate = afterCreateOrUpdate;

 function beforeSchemaValidation(object, context) {
     /* Insert code here */
     return object;

 function objectForIndexing(object, context) {
     /* Insert code here */
     return object;

 function onObjectResolution(object, context) {
     /* Insert code here */
     return object;

 function onPayloadResolution(object, context) {
     /* Insert code here; use context.directIo to write payload bytes */

 function beforeDelete(object, context) {
     /* Insert code here */

 function afterDelete(object, context) {
     /* Insert code here */

 function afterCreateOrUpdate(object, context) {
     /* Insert code here */

Cordra provides two convenience JavaScript modules that can be imported for use within your JavaScript rules. These modules allow you to search and retrieve objects, and verify hashes and secrets. Additional modules allow you to retrieve schemas and associated JavaScript hooks, as discussed here. You can optionally include these modules in your JavaScript, as shown on Lines 1-4.

You can also save external JavaScript libraries in Cordra for applying complex logic as discussed here.

Lines 6-12 export references to the 7 hooks that Cordra enables on a Type object: beforeSchemaValidation, objectForIndexing, onObjectResolution, onPayloadResolution, beforeDelete, afterDelete and afterUpdateOrCreate,. When handling objects, Cordra will look for methods with these names and run them if found. The methods must be exported in order for Cordra to see them. None of the methods is mandatory. You only need to implement the ones you want.

Resolution of payloads will activate both onObjectResolution and onPayloadResolution. If onPayloadResolution accesses the output via context.directIo (see Direct I/O) the hook will fully control the output bytes, and the stored payload will not be directly returned to the client.

The rest of the example shell shows the boilerplate for the methods. All take both an object and a context. object is the JSON representation of the Cordra object. It may be modified and returned by beforeSchemaValidation, objectForIndexing, and onObjectResolution.

object contains id, type, content, acl, metadata, and payloads (which has payload metadata, not the full payload data). content is the user defined JSON of the object.

object has the following format:

    "id": "test/abc",
    "type": "Document",
    "content": { },
    "acl": {
        "readers": [
        "writers": [
    "metadata": {
        "createdOn": 1532638382843,
        "createdBy": "admin",
        "modifiedOn": 1532638383096,
        "modifiedBy": "admin",
        "txnId": 967

context is an object with several useful properties.

Property Name Value
isNew Flag which is true for creations and false for modifications. Applies to beforeSchemaValidation.
objectId The id of the object.
userId The id of the user performing the operation.
groups A list of the ids of groups to which the user belongs.
effectiveAcl The computed ACLs for the object, either from the object itself or inherited from configuration. This is an object with “readers” and “writers” properties.
aclCreate The creation ACL for the type being created, in beforeSchemaValidation for a creation.
newPayloads A list of payload metadata for payloads being updated, in beforeSchemaValidation for an update operation.
payloadsToDelete A list of payload names of payloads being deleted, in beforeSchemaValidation for an update operation.
requestContext A user-supplied requestContext query parameter.
payload The payload name for onPayloadResolution.
start, end User-supplied start and end points for a partial payload resolution for onPayloadResolution.
params The input supplied to a Type Methods call.
directIo Can be used for more control over input/output with Type Methods or onPayloadResolution; see Direct I/O.
isSearch Flag set to true in onObjectResolution if the call is being made to produce search results.
isDryRun Set on a create or update according to the “dryRun” parameter. Could be used in beforeSchemaValidation or afterCreateOrUpdate to prevent external side effects.
method Set to the method name in afterCreateOrUpdate when activated after an updating type method call rather than an ordinary create or update.
originalObject The object before it was updated, in afterCreateOrUpdate.
beforeSchemaValidationResult The object after beforeSchemaValidation but before other processing, notably before the removal of properties with secureProperty, in afterCreateOrUpdate.

Hooks for the Design Object and Type Objects

The design object (of type CordraDesign) and Type objects (of type Schema) do not have separate Type objects. These built-in types can still have lifecycle hooks, however. Their JavaScript modules can be defined under a property “builtInTypes” of the design object, specifically “builtInTypes.CordraDesign.javascript” and “builtInTypes.Schema.javascript”. See Design Object.

Generate Object Id Hook

This hook, that is to be stored in the Design object, is for generating object ids when objects are created. The JavaScript can be edited by selecting Design JavaScript from the Admin menu on the UI. The hook will be bound to the property “javascript” in the Design object (so it can be edited there too, see Design Object).

The shell for this hook is as follows:

exports.generateId = generateId;
exports.isGenerateIdLoopable = true;

function generateId(object, context) {
   var id;
   /* Insert code here */
   return id;

The flag isGenerateIdLoopable when set to true tells Cordra that if an object with the same id already exists this method can be called repeatedly until a unique id is found. If the implementation of generateId was deterministic, which is to say it would always return the same id for a given input object, the isGenerateIdLoopable should NOT be set to true.

Customize Query Hook

This hook is also looked for in the “javascript” property of the design object. It will be executed for every user-supplied query to the Search API. It can be used for example to restrict the query to exclude certain objects based on the calling user.

The shell for this hook is as follows:

exports.customizeQuery = customizeQuery;

function customizeQuery(query, context) {
   /* Insert code here */
   return newQuery;

Create Handle Values Hook

This hook is for specifying the handle record that is to be returned when handles are resolved using handle client tools. This hook is on the separate Design object property design.handleMintingConfig.javascript, which can be edited by selecting Handle Records from the Admin menu on the UI.

The shell for this hook is as follows:

exports.createHandleValues = createHandleValues;

function createHandleValues(object, context) {
   var handleValues = [];
   /* Insert code here */
   return handleValues;

If creating handle values with JavaScript it is important to consider that all Cordra objects, even if not publicly visible, will have a handle record created. If you are storing data directly in the handle record you may wish to check if the Cordra object is publicly accessible. You can do this by inspecting the ‘context’ argument. For example:

function isPublic(context) {
    var effectiveAcl = context.effectiveAcl;
    if (effectiveAcl.writers && effectiveAcl.writers.indexOf("public") !== -1) {
        return true;
    } else if (effectiveAcl.readers && effectiveAcl.readers.indexOf("public") !== -1) {
        return true;
    } else {
        return false;

Throwing Errors in Schema JavaScript

Errors thrown in as strings will end up in the server response, with the thrown string as the error message.

throw "An error has occurred";

If the user requests are issued via the REST API, for beforeSchemaValidation and Type methods calls, this will be returned to the user as a 400 Bad Request. For onObjectResolution and beforeDelete, this will be returned as 403 Forbidden. For search results where onObjectResolution throws an exception, the corresponding object will be omitted from the search results (this can affect search results count).

If the user requests are issued via the DOIP interface, a “bad request” or “forbidden” error will be returned.

For more control over the server response, you can also throw a custom CordraError, available via the Cordra.js Module. For example:

const cordra = require("cordra");

const responseCode = 418; // defaults to 400 or (for resolution) 403 if undefined
const response = {
    message: "Beverage Not Supported",
    requestedBeverage: "coffee",
    supportedBeverages: ["tea", "water"]
throw new cordra.CordraError(response, responseCode);

This will be translated into a server response with the given error message and response status code. If present, the response object will be added to the body of the server response. This can be used to send extra information about the error back to the caller.

As a convenience, if the first argument to new cordra.CordraError is a string, the response will be {"message":"that string"}.

Thrown errors other than strings and CordraError will be seen by the user as 500 Internal Server Error.

Cordra Modules

Cordra.js Module

The builtin Cordra.js module has helpful functions, listed below, that may be useful when writing JavaScript code in Type methods.

Note: Lifecycle hooks are triggered when calls are made using the external APIs. Calls made to Cordra using the helpful functions in the cordra.js module do not trigger any lifecycle hooks.


Use get to get an object from Cordra by the object ID:


If an object with the given ID is found, it will be returned. Otherwise, null will be returned.

Payload Retrieval

Use any of the following to retrieve a payload from Cordra using the object ID and the payload name:

cordra.getPayloadAsJavaInputStream(objectId, payloadName);
cordra.getPayloadAsJavaReader(objectId, payloadName);
cordra.getPayloadAsUint8Array(objectId, payloadName);
cordra.getPayloadAsString(objectId, payloadName);
cordra.getPayloadAsJson(objectId, payloadName);
cordra.getPartialPayloadAsJavaInputStream(objectId, payloadName, start, end);
cordra.getPartialPayloadAsJavaReader(objectId, payloadName, start, end);
cordra.getPartialPayloadAsUint8Array(objectId, payloadName, start, end);
cordra.getPartialPayloadAsString(objectId, payloadName, start, end);

CordraUtil.js Module

Escape for Query

Will modify a string for literal inclusion in a phrase query for calling

var query = '/property:"' + cordraUtil.escapeForQuery(s) + '"';

Verify Secret

Used to verify a given string against the hash stored for that property:

cordraUtil.verifySecret(obj, jsonPointer, secretToVerify);

Return true or false, depending on the results of the verification.

Verify Hashes

Verifies the hashes on a cordra object property:


Returns a verification report object indicating which of the object hashes verify.

Hash Json

Hashes a JSON object, JSON array or primitive:

cordraUtil.hashJson(jsonElement, algorithm);

Returns a base16 encoded string of the SHA-256 hash (or other specified algorithm) of the input. The input JSON is first canonicalized before being hashed.

Sign With Key

Signs a payload (a string) with a given private key in JWK format:

const jws = cordraUtil.signWithKey(payload, jwk);

Returns a Json Web Signature in compact serialization.

Sign With Cordra Key

Signs a payload (a string) with the private key of the Cordra instance:

const jws = cordraUtil.signWithCordraKey(payload);

Returns a Json Web Signature in compact serialization.

The private key used is the same key used for administering an external handle server, and can be set by including a file “privatekey” in the Cordra data directory. See Handle Server and also Cordra Configuration for the distributed version.

Retrieve Cordra Public Key

Returns the Cordra public key in JWK format:

const jwk = cordraUtil.getCordraPublicKey();

Verify With Cordra Key

Verifies a JWS with the private key of the Cordra instance:

const isValid = cordraUtil.verifyWithCordraKey(jws);

Extract JWT Payload

Extracts the payload of a JWT, returning the parsed JSON as a JavaScript object:

const claimsObject = cordraUtil.extractJwtPayload(jws);

Cordra Schemas and JavaScript

Schemas associated with type objects are available to the JavaScript via require('/cordra/schemas/Type.schema.json'), and JavaScript added to those type objects via require('/cordra/schemas/Type'). Here Type should be replaced by the name of the particular type to be accessed.

Using External Modules

External JavaScript modules can be managed with a Cordra object as a payload configured to be a “referrable” source of JavaScript modules. Typically, this can be done on a single object of a type called JavaScriptDirectory. Here are the steps needed to create and populate the JavaScriptDirectory object.

  1. Create a new schema in Cordra called “JavaScriptDirectory” and using the “javascript-directory” template.
  2. Create a new JavaScriptDirectory object. Set the directory to /node_modules. This will allow you to import modules by filename, instead of directory path and filename.
  3. Add your JavaScript module files as payloads to the JavaScriptDirectory object. The payload name should match the filename and will be used when importing a module. For example, a payload named util.js could be importing using require('util');

The use of external JavaScript modules affects reindexing. It is currently necessary to ensure that objects of type “Schema” and any sources of JavaScript (like type “JavaScriptDirectory”) are indexed first. See Reindexing for information.

JavaScript Version and Limitations

Cordra uses the Nashorn JavaScript Engine packaged with Java. The version of JavaScript supported depends on the version of Java used to run Cordra. Java 8 supports ECMAScript 5.1. As of the time of this writing, Java 9 supports some but not all ECMAScript 6 features.

Cordra JavaScript does come prepopulated with a wide range of polyfills allowing features up to ECMAScript 2017. It is thus in many cases straightforward to write ECMAScript 2017 code and transpile it (using for example Babel) to ES5 for use in Cordra.

In Java, there is a limit on the size of a single function. It is rare to run up against this limit writing Java code, but it can happen when JavaScript is compiled to Java. This is especially true when using third-party libraries, which may be minified in one large function. If you hit this limit, you will see the error “Code Too Large” in your logs.

Legacy JavaScript Hooks

In early versions of the Cordra 2.0 Beta software, the JavaScript hooks beforeSchemaValidation, onObjectResolution, and beforeDelete took the JSON content of the Cordra object, instead of the full Cordra object (including id, type, content, acl, metadata, and payloads properties). Additionally the JavaScript cordra.get function returned only the content instead of the full Cordra object.

If a Cordra instance with JavaScript written for those earlier versions needs to be upgraded, and it is not yet possible to adapt the JavaScript to the current API, then the following flag must be added to the Design object:

"useLegacyContentOnlyJavaScriptHooks": true

For more information on editing the Design object, see Design Object.

Cordra users upgrading from early versions of the Cordra 2.0 beta, who did not use schema JavaScript (apart from the default User schema JavaScript, which will be automatically upgraded if it has not been edited), do not in general need to take any action.

Examples of Hooks

Example: User Schema JavaScript

The default Cordra User schema comes with JavaScript that performs basic password validation.

var cordra = require("cordra");

exports.beforeSchemaValidation = beforeSchemaValidation;

function beforeSchemaValidation(object, context) {
    if (! = "";
    if (!object.content.password) object.content.password = "";
    var password = object.content.password;
    if (context.isNew || password) {
        if (password.length < 8) {
            throw "Password is too short. Min length 8 characters";
    return object;

This code will run before the given object is validated and stored. If this request is a create (context.isNew is true) or contains a password, the password is checked to make sure it is long enough. If not, an error is thrown. This error will be returned to the callee and can be displayed as desired.

Example: Document Modification

In this slightly more complicated example, we will bind lifecycle hooks to the Document type pre-defined in Cordra with the following features:

  • Add a timestamp to the description of the document in a way it is stored.
  • Add a timestamp to the description when the object is resolved, but not actually store.
  • Require that the description be changed to “DELETEME” before the document can be deleted.

To demonstrate loading JavaScript from an external file, the function to create the timestamp is in a file called util.js. Create a JavaScript Directory (as described above) and upload this file as a payload named util.js.

exports.getTimestampString = getTimestampString;

function getTimestampString(isResolution) {
    var currentDate = new Date();
    if (isResolution) {
        return '\nResolved at: ' + currentDate;
    } else {
        return '\nLast saved: ' + currentDate;

Next, edit the Document type in Cordra and put the following in the JavaScript field.

var util = require('util');

exports.beforeSchemaValidation = beforeSchemaValidation;
exports.onObjectResolution = onObjectResolution;
exports.beforeDelete = beforeDelete;

function beforeSchemaValidation(object, context) {
    if (object.content.description !== 'DELETEME') {
        object.content.description += util.getTimestampString(false);
    return object;

function onObjectResolution(object, context) {
    object.content.description += util.getTimestampString(true);
    return object;

function beforeDelete(object, context) {
    if (object.content.description !== 'DELETEME') {
        throw 'Description must be DELETEME before object can be deleted.';

Finally, create a new document in Cordra. You should see that whenever the document is updated, a new timestamp is appended to the description. If you view the document’s JSON, you should see a single resolution timestamp, which changes on every resolution. Finally, if you try to delete the document without changing the description to “DELETEME” you should see an error message.

Example: Modification of the Indexed Object

It is possible make changes to the object that is indexed such that it differs from the object that is stored. This is achieved by writing a function called objectForIndexing.

exports.objectForIndexing = objectForIndexing;

function objectForIndexing(object, context) {
    if ( == "foo") {
        object.content.otherName = "bar";
    return object;

In this example if the incoming object has a property called name with the value foo, a new property will be added to the indexed object called otherName with the value bar. The object that is stored will not contain the new property but you will be able to search for this object via this property with the query /otherName:bar.

Example: Generating ID

Example JavaScript for generating object ids is shown below. Here we generate a random suffix for the handle in base16 and append it to a prefix. By setting isGenerateIdLoopable to true, we ask Cordra to repeatedly call this method until a unique id is generated.

var cordra = require('cordra');

exports.generateId = generateId;
exports.isGenerateIdLoopable = true;

function generateId(object, context) {
    return "test/" + randomSuffix();

function randomSuffix() {
    return Math.random().toString(16).substr(2);

Example: Creating Handle Values

Example JavaScript for creating handle values is shown below. The JavaScript puts a copy of the information from the Cordra object in the Handle record.

exports.createHandleValues = createHandleValues;

function createHandleValues(object) {
    var handleValues = [];
    var dataValue = {
        index: 500,
        type: 'CORDRA_OBJECT',
        timestamp: new Date(object.metadata.modifiedOn).toISOString(),
        data: {
            format: 'string',
            value: JSON.stringify(object.content)
    return handleValues;