Triggers

In a trigger you register Cypher statements that are called when data in Neo4j is changed (created, updated, deleted). You can run them before or after commit.

Enable apoc.trigger.enabled=true in $NEO4J_HOME/conf/apoc.conf first.

CALL apoc.trigger.add(name, statement, selector) yield name, statement, installed

add a trigger statement under a name, in the statement you can use $createdNodes, $deletedNodes etc., the selector is {phase:'before/after/rollback/afterAsync'} returns previous and new trigger information, please check Trigger Phase Table for more details

CALL apoc.trigger.remove(name) yield name, statement, installed

remove previously added trigger, returns trigger information

CALL apoc.trigger.removeAll() yield name, statement, installed

removes all previously added triggers , returns trigger information

CALL apoc.trigger.list() yield name, statement, installed

update and list all installed triggers

CALL apoc.trigger.pause(name)

it pauses the trigger

CALL apoc.trigger.resume(name)

it resumes the paused trigger

The transaction data from Neo4j is turned into appropriate data structures to be consumed as parameters to your statement, i.e. $createdNodes.

The parameters available are:

Statement Description

transactionId

returns the id of the transaction

commitTime

return the date of the transaction in milliseconds

createdNodes

when a node is created our trigger fires (list of nodes)

createdRelationships

when a relationship is created our trigger fires (list of relationships)

deletedNodes

when a node is delated our trigger fires (list of nodes)

deletedRelationships

when a relationship is delated our trigger fires (list of relationships)

removedLabels

when a label is removed our trigger fires (map of label to list of nodes)

removedNodeProperties

when a properties of node is removed our trigger fires (map of key to list of map of key,old,node)

removedRelationshipProperties

when a properties of relationship is removed our trigger fires (map of key to list of map of key,old,relationship)

assignedLabels

when a labes is assigned our trigger fires (map of label to list of nodes)

assignedNodeProperties

when node property is assigned our trigger fires (map of key to list of map of key,old,new,node)

assignedRelationshipProperties

when relationship property is assigned our trigger fires (map of key to list of map of key,old,new,relationship)

You can use these helper functions to extract nodes or relationships by label/relationship-type or updated property key.

Table 1. Helper Functions

apoc.trigger.nodesByLabel($assignedLabels/$assignedNodeProperties,'Label')

function to filter entries by label, to be used within a trigger statement with $assignedLabels and $removedLabels

apoc.trigger.propertiesByKey($assignedNodeProperties,'key')

function to filter propertyEntries by property-key, to be used within a trigger statement with $assignedNode/RelationshipProperties and $removedNode/RelationshipProperties. Returns [{old,[new],key,node,relationship}]

Triggers Examples

Set properties connected to a node

We could add a trigger that when is added a specific property on a node, that property is added to all the nodes connected to this node

Dataset

CREATE (d:Person {name:'Daniel'})
CREATE (l:Person {name:'Mary'})
CREATE (t:Person {name:'Tom'})
CREATE (j:Person {name:'John'})
CREATE (m:Person {name:'Michael'})
CREATE (a:Person {name:'Anne'})
CREATE (l)-[:DAUGHTER_OF]->(d)
CREATE (t)-[:SON_OF]->(d)
CREATE (t)-[:BROTHER]->(j)
CREATE (a)-[:WIFE_OF]->(d)
CREATE (d)-[:SON_OF]->(m)
CREATE (j)-[:SON_OF]->(d)
apoc.trigger.add.setAllConnectedNodes.dataset

Now we add the trigger using apoc.trigger.propertiesByKey on the surname property

CALL apoc.trigger.add('setAllConnectedNodes','UNWIND apoc.trigger.propertiesByKey($assignedNodeProperties,"surname") as prop
WITH prop.node as n
MATCH(n)-[]-(a)
SET a.surname = n.surname', {phase:'after'});

So when we add the surname property on a node, it’s added to all the nodes connected (in this case one level deep)

MATCH (d:Person {name:'Daniel'})
SET d.surname = 'William'
apoc.trigger.add.setAllConnectedNodes

The surname property is add/change on all related nodes

Update labels

Dataset

CREATE (k:Actor {name:'Keanu Reeves'})
CREATE (l:Actor {name:'Laurence Fishburne'})
CREATE (c:Actor {name:'Carrie-Anne Moss'})
CREATE (m:Movie {title:'Matrix'})
CREATE (k)-[:ACT_IN]->(m)
CREATE (l)-[:ACT_IN]->(m)
CREATE (c)-[:ACT_IN]->(m)
apoc.trigger.add.setLabels

We add a trigger using apoc.trigger.nodesByLabel that when the label Actor of a node is removed, update all labels Actor with Person

CALL apoc.trigger.add('updateLabels',"UNWIND apoc.trigger.nodesByLabel($removedLabels,'Actor') AS node
MATCH (n:Actor)
REMOVE n:Actor SET n:Person SET node:Person", {phase:'before'})
MATCH(k:Actor {name:'Keanu Reeves'})
REMOVE k:Actor
apoc.trigger.add.setLabelsResult
Create relationship on a new node

We can add a trigger that connect every new node with label Actor and as name property a specific value

CALL apoc.trigger.add('create-rel-new-node',"UNWIND $createdNodes AS n
MATCH (m:Movie {title:'Matrix'})
WHERE n:Actor AND n.name IN ['Keanu Reeves','Laurence Fishburne','Carrie-Anne Moss']
CREATE (n)-[:ACT_IN]->(m)", {phase:'before'})
CREATE (k:Actor {name:'Keanu Reeves'})
CREATE (l:Actor {name:'Laurence Fishburne'})
CREATE (c:Actor {name:'Carrie-Anne Moss'})
CREATE (a:Actor {name:'Tom Hanks'})
CREATE (m:Movie {title:'Matrix'})
apoc.trigger.add.create rel new node
Pause trigger

We have the possibility to pause a trigger without remove it, if we will need it in the future

apoc.trigger.pause
Resume paused trigger

When you need again of a trigger paused

apoc.trigger.resume
Enforcing property type

For this example, we would like that all the reference node properties are of type STRING

CALL apoc.trigger.add("forceStringType",
"UNWIND apoc.trigger.propertiesByKey($assignedNodeProperties, 'reference') AS prop
CALL apoc.util.validate(apoc.meta.type(prop) <> 'STRING', 'expected string property type, got %s', [apoc.meta.type(prop)]) RETURN null", {phase:'before'})
CREATE (a:Node) SET a.reference = 1

Neo.ClientError.Transaction.TransactionHookFailed
Other examples
CALL apoc.trigger.add('timestamp','UNWIND $createdNodes AS n SET n.ts = timestamp()');
CALL apoc.trigger.add('lowercase','UNWIND $createdNodes AS n SET n.id = toLower(n.name)');
CALL apoc.trigger.add('txInfo',   'UNWIND $createdNodes AS n SET n.txId = $transactionId, n.txTime = $commitTime', {phase:'after'});
CALL apoc.trigger.add('count-removed-rels','MATCH (c:Counter) SET c.count = c.count + size([r IN $deletedRelationships WHERE type(r) = "X"])')
CALL apoc.trigger.add('lowercase-by-label','UNWIND apoc.trigger.nodesByLabel($assignedLabels,'Person') AS n SET n.id = toLower(n.name)')
Table 2. Helper Functions

Phase

Description

before

The trigger will be activate right before the commit. If no phase is specified, it’s the default.

rollback

The trigger will be activate right after the rollback

after

The trigger will be activate right after the commit

afterAsync

The trigger will be activate right after the commit and inside a new transaction and thread that will not impact the original one. Heavy operations should be processed in this phase without blocking the original transaction.