# How to Create Unique Indexes and Constraints on Properties in neomodel

> Learn to create unique indexes and constraints on properties in neomodel. Effortlessly enforce data uniqueness in your Neo4j database with simple Python code.

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

---

**Use `unique_index=True` when defining a `Property` in your `StructuredNode` or `StructuredRel` class, then run `db.install_labels()` to generate the corresponding `CREATE CONSTRAINT ... IS UNIQUE` statement in Neo4j.**

Neomodel, the Python Object-Graph Mapper (OGM) for Neo4j maintained by the `neo4j-contrib` organization, provides a declarative way to enforce data integrity through unique constraints. By setting `unique_index=True` on a property definition, you instruct neomodel to create a database-level unique constraint rather than a standard index, ensuring no two nodes or relationships share the same value for that property.

## Understanding Unique Constraints vs Indexes in neomodel

In neomodel, the terms "unique index" and "unique constraint" refer to the same database artifact. When you declare a property with `unique_index=True`, neomodel generates a Neo4j `IS UNIQUE` constraint, which both enforces uniqueness and creates an index for fast lookups.

**Important:** The `unique_index` and `index` parameters are mutually exclusive. In [`neomodel/properties.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/properties.py), the `Property` base class validates this during initialization:

```python

# neomodel/properties.py

if unique_index and index:
    raise ValueError(
        "The arguments `unique_index` and `index` are mutually exclusive."
    )

```

You must choose one or the other: use `index=True` for non-unique indexes and `unique_index=True` for unique constraints.

## Creating Unique Constraints on Node Properties

### Defining Unique Properties in Your Model

To enforce uniqueness on a node property, subclass `StructuredNode` and set `unique_index=True` on the desired `Property`:

```python
from neomodel import StructuredNode, StringProperty, UniqueIdProperty

class User(StructuredNode):
    # Automatically generates a unique constraint on the uid property

    uid = UniqueIdProperty()
    
    # Explicit unique constraint on email

    email = StringProperty(unique_index=True, required=True)
    username = StringProperty()

```

The `UniqueIdProperty` is a convenience class that automatically sets `unique_index=True` and generates a UUID, but you can apply `unique_index=True` to any property type including `StringProperty`, `IntegerProperty`, etc.

### Installing Constraints to Neo4j

Defining the model only updates the Python class; you must explicitly install the schema to the database. Neomodel provides three ways to trigger constraint creation:

**Method 1: Install specific labels**

```python
from neomodel import db

# Connect to Neo4j first

db.set_connection('bolt://neo4j:password@localhost:7687')

# Install constraints for the User model only

db.install_labels(User)

```

**Method 2: Install all labels**

```python

# Discover and install all StructuredNode subclasses

db.install_all_labels()

```

**Method 3: Command-line interface**

```bash
python -m neomodel.scripts.neomodel_install_labels \
    path/to/models.py path/to/other_models.py

```

When you run any of these commands, neomodel executes Cypher statements like:

```cypher
CREATE CONSTRAINT constraint_unique_User_email
FOR (n:User) REQUIRE n.email IS UNIQUE

```

If the constraint already exists, neomodel catches the `ClientError` and continues silently, making the operation idempotent.

## Creating Unique Constraints on Relationship Properties (Neo4j 5.7+)

Neo4j only supports unique constraints on relationship properties starting from version 5.7. Neomodel checks the server version during schema installation and raises `FeatureNotSupported` if you attempt to create a unique constraint on a relationship property in earlier versions.

To define a unique constraint on a relationship property, pass `unique_index=True` to the property definition within the `RelationshipTo` or `RelationshipFrom` declaration:

```python
from neomodel import StructuredNode, RelationshipTo, StringProperty

class City(StructuredNode):
    name = StringProperty()

class Country(StructuredNode):
    name = StringProperty(unique_index=True)
    # Unique constraint on the 'code' property of the LOCATED_IN relationship

    cities = RelationshipTo(City, "LOCATED_IN", 
                           code=StringProperty(unique_index=True))

```

When you run `db.install_all_labels()` on Neo4j 5.7+, neomodel executes:

```cypher
CREATE CONSTRAINT constraint_unique_LOCATED_IN_code
FOR ()-[r:LOCATED_IN]-() REQUIRE r.code IS UNIQUE

```

On older Neo4j versions, the installation fails with a clear error message indicating that relationship property uniqueness constraints require Neo4j 5.7 or later.

## How Unique Constraints Work Under the Hood

Understanding the implementation details helps debug schema installation issues and optimize your data model.

### Property Definition Validation

In [`neomodel/properties.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/properties.py), the `Property` class constructor (lines 91-99 and 146-148) validates that you cannot combine `index` and `unique_index`:

```python

# From neomodel/properties.py

class Property:
    def __init__(self, unique_index=False, index=False, **kwargs):
        if unique_index and index:
            raise ValueError(
                "The arguments `unique_index` and `index` are mutually exclusive."
            )
        self.unique_index = unique_index
        # ... additional initialization

```

### Schema Installation Logic

The `Database` class in [`neomodel/sync_/database.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/sync_/database.py) (and its async counterpart in [`neomodel/async_/database.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/async_/database.py)) handles the actual constraint creation:

1. **`_install_node()`** (around line 1322) iterates through node properties and calls `_create_node_constraint()` for any property with `unique_index=True`.

2. **`_create_node_constraint()`** generates the Cypher:
   ```python
   label = target_cls.__label__
   constraint_name = f"constraint_unique_{label}_{property_name}"
   self.cypher_query(
       f"""CREATE CONSTRAINT {constraint_name}
                   FOR (n:{label}) REQUIRE n.{property_name} IS UNIQUE"""
   )
   ```

3. **Error handling**: The method catches `ClientError` to handle cases where the constraint already exists, making the operation safe to run multiple times.

For relationships, `_install_relationship()` performs a version check before calling `_create_relationship_constraint()`, ensuring compatibility with Neo4j 5.7+.

## Summary

- **Use `unique_index=True`** on any `Property` subclass to enforce uniqueness at the database level.
- **Constraints are not created automatically**; you must run `db.install_labels(Model)` or `db.install_all_labels()` to apply them to Neo4j.
- **Mutual exclusivity**: You cannot use `unique_index=True` and `index=True` on the same property.
- **Relationship constraints** require Neo4j 5.7 or later; neomodel raises `FeatureNotSupported` on older versions.
- **Idempotent operation**: Running the installation script multiple times is safe—existing constraints are silently skipped.

## Frequently Asked Questions

### What is the difference between `index=True` and `unique_index=True` in neomodel?

The `index=True` parameter creates a standard Neo4j index that speeds up lookups but allows duplicate values. The `unique_index=True` parameter creates a unique constraint that both indexes the property and enforces that no two nodes or relationships can share the same value. According to the source code in [`neomodel/properties.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/properties.py), these two options are mutually exclusive—you must choose one or the other.

### How do I apply unique constraints to an existing Neo4j database?

After defining or modifying your neomodel classes to include `unique_index=True` on the desired properties, connect to your database using `db.set_connection()` and run `db.install_labels(YourModel)` for specific models or `db.install_all_labels()` to apply all constraints. This executes the necessary `CREATE CONSTRAINT` Cypher statements. The operation is idempotent, so running it multiple times will not cause errors if the constraints already exist.

### Can I create unique constraints on relationship properties in neomodel?

Yes, but only if you are running Neo4j version 5.7 or later. To define a unique constraint on a relationship property, pass `unique_index=True` to the property definition within your `RelationshipTo` or `RelationshipFrom` declaration. When you run the schema installation, neomodel checks the server version in [`neomodel/sync_/database.py`](https://github.com/neo4j-contrib/neomodel/blob/main/neomodel/sync_/database.py) and raises `FeatureNotSupported` if the database is older than 5.7. For supported versions, it creates the constraint using `CREATE CONSTRAINT ... FOR ()-[r:REL_TYPE]-() REQUIRE r.property IS UNIQUE`.