Introduction
In the previous lesson, you learned the three-step GDS workflow: Project → Run → Write. You saw how this pattern enables fast, safe, iterative analysis by separating your source data from algorithmic operations.
This lesson focuses exclusively on Step 1: Projection—the foundation of all GDS work.
By the end of this lesson, you will understand:
-
How Cypher projections work in detail
-
What graph structures you’re creating when you project
-
Why different projection types matter for algorithms
Cypher Projection Anatomy
Let’s revisit the example from the previous lesson and break it down completely.
MATCH (source:Actor)-[r:ACTED_IN]->(target:Movie) // (1)
WITH gds.graph.project( // (2)
'actors-graph', // (3)
source, // (4)
target // (5)
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels // (6)-
Match Actor nodes connected to Movie nodes via ACTED_IN relationships
-
Call the GDS projection function
-
Name the projection 'actors-graph'
-
Include source nodes (Actors) in the projection
-
Include target nodes (Movies) in the projection
-
Return projection statistics
This projection has three main components:
1. The Cypher Pattern
MATCH (source:Actor)-[r:ACTED_IN]->(target:Movie) // (1)-
Match Actor nodes connected to Movie nodes via ACTED_IN relationships
This is standard Cypher. You’re matching a pattern in your database:
-
sourcenodes with theActorlabel -
targetnodes with theMovielabel -
ACTED_INrelationships connecting them
The variable names source and target are important—you’ll reference them in the projection.
However, just like the writeProperty value we created in the previous lesson, these variables are inherently arbitrary.
We could run the following, equally valid projection:
source and target in production code for clarity and maintainability.MATCH (bananas:Actor)-[:ACTED_IN]->(cabbages:Movie) // (1)
WITH gds.graph.project( // (2)
'actors-graph', // (3)
bananas, // (4)
cabbages // (5)
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels // (6)-
Match Actor and Movie nodes with arbitrary variable names
-
Call the GDS projection function
-
Name the projection 'actors-graph'
-
Include bananas (Actor) nodes in the projection
-
Include cabbages (Movie) nodes in the projection
-
Return projection statistics
The variables you use for the nodes in your Cypher query, are the variables you will call as nodes in the graph projection.
As a general standard we use source and target only because many other projection configuration settings will refer to the first variable as source and the second as target.
Defaulting to source and target for the initial variables just makes it easier to keep track.
2. The projection call
WITH gds.graph.project( // (1)
'actors-graph', // (2)
source, // (3)
target, // (4)
{}, // (5)
{} // (6)
) AS g-
Call the GDS projection function
-
Name the projection 'actors-graph'
-
Include source nodes in the projection
-
Include target nodes in the projection
-
First configuration map (for relationship properties)
-
Second configuration map (for projection options)
The WITH clause pipes your matched pattern into gds.graph.project():
'actors-graph': The name you’ll use to reference this projection when running algorithms
source: Nodes matched by your source variable become nodes in the projection
target: Nodes matched by your target variable also become nodes in the projection
Relationships: Automatically inferred from the pattern between source and target
The curly brackets are there to house your other configuration settings. To create a simple projection, you do not need them. In fact, you could rewrite the same projection command without even referencing them.
WITH gds.graph.project( // (1)
'actors-graph', // (2)
source, // (3)
target // (4)
) AS g-
Call the GDS projection function
-
Name the projection 'actors-graph'
-
Include source nodes in the projection
-
Include target nodes in the projection
We include them here just to acknowledge them, and remind you that they still exist in the background.
3. The return statement
You have already dealt with this — it’s really no different from your normal return query when using pure Cypher.
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels // (1)-
Return the graph name, node count, and relationship count
This returns metadata about your projection:
-
How many nodes were projected
-
How many relationships were created
-
The graph name for verification
Try it yourself
Now that you understand how projections work, complete the query below to create a projection of Actors and Movie nodes.
Replace the ????? placeholders with the correct values:
MATCH (source:?????)-[r:ACTED_IN]->(target:?????) // (1)
WITH gds.graph.project( // (2)
'my-projection', // (3)
?????, // (4)
????? // (5)
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels // (6)-
Match source and target nodes (fill in the labels)
-
Call the GDS projection function
-
Name the projection 'my-projection'
-
Include source nodes (fill in the variable)
-
Include target nodes (fill in the variable)
-
Return projection statistics
Once you’ve completed the query, run it to verify your projection was created successfully.
If you need help filling in the query, open the dropdown below for the full solution.
Details
MATCH (source:Actor)-[r:ACTED_IN]->(target:Movie) // (1)
WITH gds.graph.project( // (2)
'my-projection', // (3)
source, // (4)
target // (5)
) AS g
RETURN g.graphName AS graph, g.nodeCount AS nodes, g.relationshipCount AS rels // (6)-
Match Actor nodes connected to Movie nodes via ACTED_IN relationships
-
Call the GDS projection function
-
Name the projection 'my-projection'
-
Include source (Actor) nodes in the projection
-
Include target (Movie) nodes in the projection
-
Return projection statistics
Key points:
-
source:Actormatches Actor nodes -
target:Moviematches Movie nodes -
Pass
sourceandtargetvariables intogds.graph.project() -
The ACTED_IN relationships are automatically inferred from your MATCH pattern
What’s next
You now understand how Cypher projections work: you match a pattern with standard Cypher, then pipe it into gds.graph.project() to create an in-memory graph.
But there’s an important detail about what you’ve just projected that might surprise you.
In the next lesson, you’ll learn about different graph structure types and discover what type of graph your actors-movies projection actually created.
Check your understanding
Understanding Projection Components
You run this projection:
MATCH (source:Author)-[r:WROTE]->(target:Article)
WITH gds.graph.project('research-network', source, target) AS g
RETURN g.graphName, g.nodeCount, g.relationshipCountWhat nodes are included in the 'research-network' projection?
-
❏ Only Author nodes
-
❏ Only Article nodes
-
✓ Both Author nodes and Article nodes
-
❏ All nodes in the database
Hint
Look at what variables are passed into gds.graph.project(). Both source and target are included.
Solution
Both Author nodes and Article nodes is correct.
When you pass source and target to gds.graph.project(), both sets of matched nodes become part of the projection:
-
source(Author nodes) are included -
target(Article nodes) are included -
The WROTE relationships between them are automatically inferred
This creates a bipartite graph with both node types.
Summary
Cypher projections use standard MATCH patterns to define which nodes and relationships to project into memory. The basic syntax is: MATCH (source)-[r]→(target) WITH gds.graph.project('name', source, target).
A projection has three components:
-
Cypher pattern - Standard MATCH defining which nodes and relationships to include
-
Projection call -
WITH gds.graph.project()piping the matched pattern into GDS -
Return statement - Returns metadata about the created projection
In the next lesson, you’ll learn about different graph structure types and how GDS handles them.