How to Use Fulltext Indexes for Text Search in Neomodel: A Complete Guide

Neomodel enables full-text search by declaring FulltextIndex on node properties, automatically creating indexes via db.install_labels(), and querying through FulltextFilter in the .filter() API.

Fulltext indexes for text search in neomodel provide native Lucene-powered search capabilities directly through the Object Graph Mapper (OGM). By annotating properties with FulltextIndex and using FulltextFilter in queries, you can execute high-performance text searches without writing raw Cypher.

Declaring Fulltext Indexes on Node Properties

To enable full-text search, declare a FulltextIndex instance on any StringProperty in your node definition.

The FulltextIndex Class

The FulltextIndex class is defined in neomodel/properties.py (lines 47-66). It stores index configuration including the analyzer and consistency model.

from neomodel import StructuredNode, StringProperty, FulltextIndex

class Article(StructuredNode):
    title = StringProperty(fulltext_index=FulltextIndex())
    content = StringProperty(fulltext_index=FulltextIndex(eventually_consistent=True))

Configuration Options

  • analyzer: Specifies the Lucene analyzer (default: standard-no-stop-words).
  • eventually_consistent: When True, allows the index to lag behind writes for better performance.

Creating Indexes in Neo4j

Indexes are not created automatically on model definition. You must explicitly install labels to trigger index creation.

Installing Labels with db.install_labels()

Call db.install_labels() to generate the CREATE FULLTEXT INDEX Cypher statements. This method invokes Database._create_node_fulltext_index in neomodel/sync_/database.py (lines 1077-1100).

from neomodel import db

# Run once during application initialization

db.install_labels(Article)

Generated Cypher Syntax

Behind the scenes, neomodel executes Cypher similar to:

CREATE FULLTEXT INDEX fulltext_index_Article_title
FOR (n:Article) ON EACH [n.title]
OPTIONS {
    indexConfig: {
        `fulltext.analyzer`: 'standard-no-stop-words',
        `fulltext.eventually_consistent': false
    }
}

Version Requirements

Fulltext index creation requires Neo4j 5.16 or newer. The Database._create_node_fulltext_index method checks VERSION_FULLTEXT_INDEXES_SUPPORT (defined in neomodel/constants.py, lines 43-45) and raises FeatureNotSupported on older versions.

Querying Fulltext Indexes

Once indexes exist, query them using the FulltextFilter class in conjunction with the .filter() method.

Using FulltextFilter

The FulltextFilter class is defined in neomodel/semantic_filters.py (lines 31-58). It encapsulates the search string, target property, result limit, and relevance threshold.

from neomodel import FulltextFilter

results = Article.nodes.filter(
    fulltext_filter=FulltextFilter(
        query_string="graph databases",
        fulltext_attribute_name="content",
        topk=10,
        threshold=0.5
    )
).all()

The Filter API

When .filter() receives a FulltextFilter, the Match.filter method in neomodel/sync_/match.py (lines 1727-1748) extracts the filter and stores it in self.fulltext_query. During query compilation, neomodel injects a CALL db.index.fulltext.queryNodes(...) clause.

Returned Results Format

Fulltext queries return a list of tuples where each tuple contains (node_instance, relevance_score):

for article, score in results:
    print(f"Relevance: {score:.2f} - Title: {article.title}")

Combining Fulltext and Property Filters

FulltextFilter integrates seamlessly with standard property filters. You can chain fulltext search with exact matches, regex, or other semantic filters.


# Find articles with "Neo4j" in the title (property filter) 

# AND "fulltext" in the body (fulltext index)

results = Article.nodes.filter(
    title__icontains="Neo4j",
    fulltext_filter=FulltextFilter(
        query_string="fulltext",
        fulltext_attribute_name="body",
        topk=5
    )
).all()

Complete Working Example

from neomodel import (
    StructuredNode, StringProperty, db, 
    FulltextIndex, FulltextFilter
)

# 1. Define the model with fulltext indexes

class Document(StructuredNode):
    title = StringProperty(fulltext_index=FulltextIndex())
    content = StringProperty(
        fulltext_index=FulltextIndex(eventually_consistent=True)
    )

# 2. Install labels to create indexes (run once)

db.install_labels(Document)

# 3. Create sample data

Document(title="Introduction to Graphs", content="Graph databases use nodes and relationships.").save()
Document(title="Full-Text Search", content="Using fulltext indexes for efficient text search.").save()

# 4. Query the fulltext index

results = Document.nodes.filter(
    fulltext_filter=FulltextFilter(
        query_string="search",
        fulltext_attribute_name="content",
        topk=10,
        threshold=0.1
    )
).all()

# 5. Process results

for doc, score in results:
    print(f"Score: {score:.2f} | Title: {doc.title}")

Summary

  • Declare indexes using FulltextIndex in StringProperty definitions within neomodel/properties.py.
  • Create indexes by calling db.install_labels(), which triggers Database._create_node_fulltext_index in neomodel/sync_/database.py to execute CREATE FULLTEXT INDEX Cypher.
  • Query indexes by passing FulltextFilter to .filter(), handled by Match.filter in neomodel/sync_/match.py to generate CALL db.index.fulltext.queryNodes calls.
  • Requirements include Neo4j 5.16+; older versions raise FeatureNotSupported.
  • Results return as (node, score) tuples, allowing relevance-based ranking.

Frequently Asked Questions

What Neo4j version is required for fulltext indexes in neomodel?

Neomodel requires Neo4j 5.16 or newer to create and use fulltext indexes. The Database._create_node_fulltext_index method in neomodel/sync_/database.py checks against VERSION_FULLTEXT_INDEXES_SUPPORT defined in neomodel/constants.py. If you attempt to install labels on an older Neo4j instance, neomodel raises a FeatureNotSupported exception.

Can I use fulltext indexes on relationship properties?

Yes, neomodel supports fulltext indexes on both node and relationship properties. The installation process uses Database._create_relationship_fulltext_index (also in neomodel/sync_/database.py) when StructuredRel classes define properties with fulltext_index=FulltextIndex(). The query mechanism remains identical: use FulltextFilter with the relationship property name.

How do I combine fulltext search with regular property filters?

You can combine FulltextFilter with standard property lookups in the same .filter() call. The Match.filter implementation in neomodel/sync_/match.py processes both the fulltext query and property filters, combining them into a single Cypher query. For example, you can filter by title__icontains="Neo4j" while simultaneously searching the body property via FulltextFilter.

What analyzer does neomodel use by default for fulltext indexes?

By default, neomodel uses the standard-no-stop-words analyzer. This is configured in the FulltextIndex class within neomodel/properties.py. When db.install_labels() creates the index, it generates Cypher with OPTIONS { indexConfig: { 'fulltext.analyzer': 'standard-no-stop-words' } }. You can override this by passing a different analyzer string to the FulltextIndex constructor.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →