Triggers allow the registration of Cypher queries that are called when data in Neo4j is changed (created, updated, deleted). Triggers can be run before or after a commit.

The apoc.trigger.* procedures are intended to be executed in the system database, therefore they have to be executed by opening a system database session.

There are several ways of doing this:

  • When using Cypher-shell or Neo4j Browser, prefix Cypher queries with :use system

  • When using Fabric, prefix Cypher queries with USE system

  • When using the drivers, open a session directly against the system database

Moreover, the apoc.trigger procedures accept as first parameter the name of the database in which the triggers should be installed, updated, or removed.

Installing, updating, or removing a trigger is an eventually consistent operation. Therefore, they are not immediately added/updated/removed, but have a refresh rate handled by the APOC configuration apoc.trigger.refresh=<MILLISECONDS>, with default 60000 (milliseconds).

By default triggers are disabled. We can enable them by setting the following property in apoc.conf:

Table 1. Description
Option Key Value Description


true/false, default false

Enable/Disable the feature


number, default 60000

Interval in ms after which a replication check is triggered across all cluster nodes

Qualified Name Type


apoc.trigger.drop(databaseName STRING, name STRING) - eventually removes the given trigger.



apoc.trigger.dropAll(databaseName STRING) - eventually removes all triggers from the given database.



apoc.trigger.install(databaseName STRING, name STRING, statement STRING, selector MAP<STRING, ANY>, config MAP<STRING, ANY>) - eventually adds a trigger for a given database which is invoked when a successful transaction occurs.



apoc.trigger.list() - lists all currently installed triggers for the session database.

Procedure STRING) - lists all eventually installed triggers for a database.



apoc.trigger.start(databaseName STRING, name STRING) - eventually restarts the given paused trigger.



apoc.trigger.stop(databaseName STRING, name STRING) - eventually stops the given trigger.


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

The parameters available are:

Statement Description


returns the id of the transaction


returns the date of the transaction in milliseconds


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


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


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


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


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


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


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


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


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


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


a map containing the metadata of that transaction. Transaction meta data can be set on client side e.g. via

Phase parameter

The third parameter of the apoc.trigger.install() is a map {phase: PHASE}, where PHASE is a string that can have one of the following values:

Table 2. Trigger Phase Table




The trigger will be activated before the commit. If no phase is specified, the default phase is used.


The trigger will be activated right after the rollback.


The trigger will be activated after the commit.


The trigger will be activated 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. Please note that the 'after' and 'before' phases can sometimes block transactions, so generally the afterAsync phase is preferred.

Unlike previous versions of Neo4j, it will not be possible using Neo4j 5 to modify an entity created in phase: “after”. For example, the following query will return an Exception with message can’t acquire ExclusiveLock…​:

CALL apoc.trigger.install('neo4j', 'name','UNWIND $createdNodes AS n SET n.txId = $transactionId',{phase:'after'});
CREATE (f:Baz);

It is instead necessary to use another phase or perform only reading operations.

Triggers Examples

Set properties connected to a node

It is possible to add a trigger which, when added to a specific property on a node, adds the same property to all nodes connected to this node.

Dataset (in default database 'neo4j')

CREATE (d:Person {name:'Daniel', surname: 'Craig'})
CREATE (l:Person {name:'Mary', age: 47})
CREATE (t:Person {name:'Tom'})
CREATE (j:Person {name:'John'})
CREATE (m:Person {name:'Michael'})
CREATE (a:Person {name:'Anne'})
CREATE (t)-[:SON_OF]->(d)
CREATE (t)-[:BROTHER]->(j)
CREATE (a)-[:WIFE_OF]->(d)
CREATE (d)-[:SON_OF]->(m)
CREATE (j)-[:SON_OF]->(d)

With the above dataset, if a trigger is added and the following query is executed: MATCH (n:Person) WHERE IN ['Daniel', 'Mary'] SET n.age=55, n.surname='Quinn', the $assignedNodeProperties used in the trigger statement will be as follows (where NODE(1) is (:Person {name: 'Daniel'}), and NODE(2) is (:Person {name: 'Mary'})):

   age: [{
         node : NODE(1),
         new: 55,
         old: null,
         key: "age"
         node: NODE(2),
         new: 55,
         old: 47,
         key: "age"

   surname: [{
         node: NODE(1),
         new: "Quinn",
         old: "Craig",
         key: "surname"
         node: NODE(2),
         new: "Quinn",
         old: null,
         key: "surname"

The result is a map where the keys are the assigned properties, and the values are a list of entities involved. Every element of a list have the node itself, the new value of the changed properties, the old value (or null if the property didn’t exist) and the key with the property name.

The $removedNodeProperties parameter has the same structure and logic (in this case, new values will be always null).

The same is true for assignedRelationshipProperties and removedRelationshipProperties, with the only difference being that the node: NODE(n) key is replaced with the relationship: RELATIONSHIP(n) key.

As an example, the following statement creates a trigger which for every SET, updates the two properties time and lasts with the current date:

CALL apoc.trigger.install('neo4j', 'setLastUpdate',
  "UNWIND keys($assignedNodeProperties) AS k
  UNWIND $assignedNodeProperties[k] AS map
  WITH map.node AS node, collect(map.key) AS propList
  MATCH (n)
  WHERE id(n) = id(node) AND NOT 'lasts' in propList // to prevent loops
  SET n.time = date(),  n.lasts = propList",
  {phase: 'afterAsync'});

Note that the apoc.trigger.install, as well as the apoc.trigger.drop, apoc.trigger.dropAll, apoc.trigger.stop and apoc.trigger.start, have to be executed in the system database.

In the example above, MATCH (n) WHERE id(n) = id(node) is used to demonstrate that the node is found by id first, before setting its parameters. However, it is more efficient to remove this command and instead change the penultimate row to: SET node.time = date(), node.lasts = propList. Note that the condition AND NOT 'lasts' IN propList must be added to prevent an infinite loop as the SET command will trigger this query again.

It is then possible to execute the following query, after a time defined by the configuration apoc.trigger.refresh:

MATCH (n:Person {name: 'Daniel'}) set n.age = 123, = 'Italy'


MATCH (n:Person {name: 'Daniel'}) return n

It is possible to set the property time with today’s date, and lasts=['country','age'].

In cases where the surname property is added to a node, it’s added to all the nodes connected to it as well (in this case one level deep).

MATCH (d:Person {name:'Daniel'})
SET d.surname = 'William'
Create relationship on a new node

To add a trigger that connects every new node with the label Actor and assign a specific value to the name property, run the following query:

CALL apoc.trigger.install('neo4j','create-rel-new-node',"UNWIND $createdNodes AS n
MATCH (m:Movie {title:'Matrix'})
WHERE n:Actor AND 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
Prevent transaction blocking

To prevent certain transaction locks, it is generally recommended to use the afterAsync phase. This will stop the query from pending indefinitely.

Pause trigger

Note that the apoc.trigger.stop and the apoc.trigger.start procedures are eventually consistent. It is therefore necessary to wait a set amount of time for the changes to propagate. The waiting time is defined by the configuration apoc.trigger.refresh.

To pause a trigger without removing it for future purposes, use the following procedure:

Resume paused trigger

To resume a paused trigger, use the following procedure:

Optional parameters

Add \{params: {parameterMaps}} to insert additional parameters.

CALL apoc.trigger.install('neo4j', 'timeParams','UNWIND $createdNodes AS n SET n.time = $time', {}, {params: {time: timestamp()}});
Other examples
CALL apoc.trigger.install('neo4j', 'timestamp','UNWIND $createdNodes AS n SET n.ts = timestamp()', {});
CALL apoc.trigger.install('neo4j', 'lowercase','UNWIND $createdNodes AS n SET = toLower(', {});
CALL apoc.trigger.install('neo4j', 'txInfo','UNWIND $createdNodes AS n SET n.txId = $transactionId, n.txTime = $commitTime', {phase:'after'});
CALL apoc.trigger.install('neo4j', 'count-removed-rels','MATCH (c:Counter) SET c.count = c.count + size([r IN $deletedRelationships WHERE type(r) = "X"])', {})

Remove triggers

To remove the trigger with name 'test' in 'neo4j' database, run the following query:

CALL apoc.trigger.drop('neo4j', 'test')

To remove all triggers in 'neo4j' db, run the following query:

CALL apoc.trigger.dropAll('neo4j')

List of triggers

It is possible to return the full list of triggers in a database. For example, if the trigger in the following query is created:

CALL apoc.trigger.install('neo4j', 'count-removals',
    'MATCH (c:Counter) SET c.count = c.count + size([f IN $deletedNodes WHERE id(f) > 0])',

It is then possible to run (also in this case, after a time defined by the configuration apoc.trigger.refresh):

Table 3. Results
name query selector params installed paused


MATCH (c:Counter) SET c.count = c.count + size([f IN $deletedNodes WHERE id(f)  0])





Please note that, since the trigger operations are eventually consistent (based on the apoc.trigger.refresh configuration), the may return some triggers not yet added/updated/removed. To get the list of all of currently installed triggers, use the apoc.trigger.list against the session database ("neo4j" in the above case).