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

> Master fulltext indexes for text search in Neomodel. Learn to declare FulltextIndex, install labels, and query effectively with FulltextFilter for robust text search capabilities.

- Repository: [Neo4j Contrib/neomodel](https://github.com/neo4j-contrib/neomodel)
- Tags: how-to-guide
- Published: 2026-03-08

---

**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`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/properties.py) (lines 47-66). It stores index configuration including the analyzer and consistency model.

```python
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`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/sync_/database.py) (lines 1077-1100).

```python
from neomodel import db

# Run once during application initialization

db.install_labels(Article)

```

### Generated Cypher Syntax

Behind the scenes, neomodel executes Cypher similar to:

```cypher
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`](https://github.com/neo4j-contrib/neomodel/blob/main/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`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/semantic_filters.py) (lines 31-58). It encapsulates the search string, target property, result limit, and relevance threshold.

```python
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`](https://github.com/neo4j-contrib/neomodel/blob/main/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)`:

```python
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.

```python

# 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

```python
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`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/properties.py).
- **Create indexes** by calling `db.install_labels()`, which triggers `Database._create_node_fulltext_index` in [`neomodel/sync_/database.py`](https://github.com/neo4j-contrib/neomodel/blob/main/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`](https://github.com/neo4j-contrib/neomodel/blob/main/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`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/sync_/database.py) checks against `VERSION_FULLTEXT_INDEXES_SUPPORT` defined in [`neomodel/constants.py`](https://github.com/neo4j-contrib/neomodel/blob/main/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`](https://github.com/neo4j-contrib/neomodel/blob/main/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`](https://github.com/neo4j-contrib/neomodel/blob/main/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`](https://github.com/neo4j-contrib/neomodel/blob/main/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.