Handling Special Characters
This content is for v0.10.0. Switch to the latest version for up-to-date documentation.
Certain characters in property values or identifiers require special treatment in Cypher queries. This guide covers the three main categories — property values, relationship types, and property names — and the right approach for each.
Property Values
Section titled “Property Values”The Problem
Section titled “The Problem”When property values are interpolated directly into a Cypher string, control characters and punctuation can break parsing or produce silently wrong results:
# This will cause a syntax error or corrupt the queryg.query("CREATE (n:Note {text: 'It's a lovely day'})") # SyntaxError: unmatched quote
# This may parse but produce no resultsg.query("CREATE (n:Note {text: 'Line1\nLine2'})") # newline inside string literalCharacters that need special handling:
| Character | Risk |
|---|---|
' single quote |
Terminates the string literal early |
\ backslash |
Starts an escape sequence |
\n newline |
Splits the literal across lines; breaks parsing |
\r carriage return |
Same as newline |
\t tab |
Less common; can cause issues in some parsers |
" double quote |
Less common in Cypher but still problematic |
Solution 1: Parameterized Queries (Recommended)
Section titled “Solution 1: Parameterized Queries (Recommended)”Parameters bypass the Cypher string parser entirely. The value is passed as JSON outside the query text, so no escaping is required:
from graphqlite import connect
conn = connect(":memory:")
# Single quotes, newlines, backslashes — all handled automaticallyconn.cypher( "CREATE (n:Note {title: $title, content: $content})", { "title": "Alice's Report", "content": "Line 1\nLine 2\nBackslash: \\ done.", })
# Retrieve and verifyresults = conn.cypher( "MATCH (n:Note {title: $title}) RETURN n.content", {"title": "Alice's Report"})print(results[0]["n.content"])# Line 1# Line 2# Backslash: \ done.See Parameterized Queries for the full guide.
Solution 2: The Graph API
Section titled “Solution 2: The Graph API”The high-level Graph API handles escaping internally for upsert_node and upsert_edge. Pass raw Python strings; no escaping is needed:
from graphqlite import Graph
g = Graph(":memory:")
g.upsert_node("note1", { "title": "Alice's Report", "content": "Line 1\nLine 2\nBackslash: \\", "author": 'Bob said "hello"',}, label="Note")
node = g.get_node("note1")print(node["properties"]["content"]) # Line 1\nLine 2\nBackslash: \Solution 3: escape_string() for Manual Queries
Section titled “Solution 3: escape_string() for Manual Queries”If you must build a Cypher string by hand, use the escape_string() utility from the graphqlite package:
from graphqlite import Graph, escape_string
g = Graph(":memory:")
raw_text = "Alice's note:\nLine 1\nLine 2"safe_text = escape_string(raw_text) # Escapes quotes, newlines, backslashes
g.query(f"CREATE (n:Note {{content: '{safe_text}'}})")escape_string() applies these transformations in order:
\→\\(backslashes first, so they are not double-escaped)'→\'(single quotes)\n→(newlines replaced with a space)\r→(carriage returns replaced with a space)\t→(tabs replaced with a space)
If preserving newlines in stored values is important, use parameterized queries instead — escape_string() converts them to spaces.
Relationship Types
Section titled “Relationship Types”Relationship type names must be valid identifiers. The sanitize_rel_type() utility converts arbitrary strings into safe type names:
from graphqlite import sanitize_rel_type
print(sanitize_rel_type("has-friend")) # HAS_FRIENDprint(sanitize_rel_type("works with")) # WORKS_WITHprint(sanitize_rel_type("type/1")) # TYPE_1The function:
- Converts to uppercase
- Replaces hyphens, spaces, and slashes with underscores
- Strips other non-alphanumeric characters
Use it whenever relationship types come from user input or external data:
from graphqlite import Graph, sanitize_rel_type
g = Graph(":memory:")
user_provided_type = "works-with"safe_type = sanitize_rel_type(user_provided_type) # WORKS_WITH
g.upsert_edge("alice", "bob", {"project": "Apollo"}, rel_type=safe_type)Property Names and Identifiers
Section titled “Property Names and Identifiers”Backtick Quoting
Section titled “Backtick Quoting”Property names that conflict with Cypher keywords or contain special characters can be quoted with backticks:
# "type", "end", "order" are reserved Cypher keywordsg.connection.cypher("MATCH (n) WHERE n.`type` = 'A' RETURN n.`order`")
# Property names with spaces or hyphensg.connection.cypher("CREATE (n:Item {`item-code`: 'XYZ-001', `display name`: 'Widget'})")Using CYPHER_RESERVED
Section titled “Using CYPHER_RESERVED”The CYPHER_RESERVED set contains all reserved Cypher keywords. Check before using a string as a label or property name:
from graphqlite import CYPHER_RESERVED
def safe_label(name: str) -> str: if name.upper() in CYPHER_RESERVED: return f"`{name}`" return name
label = safe_label("order") # "`order`"label = safe_label("Person") # "Person"
g.connection.cypher(f"CREATE (n:{label} {{id: 'o1'}})")Common Pitfalls
Section titled “Common Pitfalls”Symptom: MATCH returns nothing after CREATE
Section titled “Symptom: MATCH returns nothing after CREATE”Cause: Newlines or carriage returns in property values broke the inline Cypher string during creation. The node was stored but its properties are corrupt or missing.
Fix: Use parameterized queries for any value that may contain whitespace control characters.
# Brokenname_with_newline = "Alice\nMitchell"g.query(f"CREATE (n:Person {{name: '{name_with_newline}'}})")
# Fixedg.connection.cypher("CREATE (n:Person {name: $name})", {"name": name_with_newline})Symptom: SyntaxError on CREATE
Section titled “Symptom: SyntaxError on CREATE”Cause: Unescaped single quotes in the value.
# Brokeng.query("CREATE (n:Quote {text: 'It's a test'})") # SyntaxError
# Fixed: use parametersg.connection.cypher("CREATE (n:Quote {text: $text})", {"text": "It's a test"})
# Or: escape manually (less preferred)g.query("CREATE (n:Quote {text: 'It\\'s a test'})")Symptom: Relationship type with hyphens not found
Section titled “Symptom: Relationship type with hyphens not found”Cause: Hyphens are not valid in unquoted Cypher identifiers. CREATE (a)-[:has-friend]->(b) is parsed as has minus friend.
Fix: Use underscores or sanitize the type name:
from graphqlite import sanitize_rel_type
# Brokeng.query("CREATE (a:Person {name: 'A'})-[:has-friend]->(b:Person {name: 'B'})")
# Fixed: use sanitized typerel_type = sanitize_rel_type("has-friend") # HAS_FRIENDg.upsert_edge("alice", "bob", {}, rel_type=rel_type)Symptom: Property access on reserved keyword property name
Section titled “Symptom: Property access on reserved keyword property name”Cause: n.type is parsed as n followed by the keyword type, not as property access.
Fix: Quote with backticks.
# Brokenresults = g.query("MATCH (n) WHERE n.type = 'Product' RETURN n")
# Fixedresults = g.query("MATCH (n) WHERE n.`type` = 'Product' RETURN n")Best Practices
Section titled “Best Practices”- Always use parameterized queries for user-supplied data. This is the only safe approach for arbitrary values.
- Use the Graph API (
upsert_node,upsert_edge) for CRUD operations. It handles escaping automatically. - Call
sanitize_rel_type()for dynamic relationship types. Any type name derived from external input needs sanitization. - Backtick-quote property names that are reserved words. Check against
CYPHER_RESERVEDwhen property names come from a schema or API response. - Validate and strip control characters at ingestion time if your data comes from sources that may embed nulls or other non-printable characters.