Nodes and their metadata are the unit of retrieval, citation and filtering in every LlamaIndex app.
A Document is a whole source, such as one file or one web page. A node (usually a TextNode) is a chunk of a document: the actual unit that gets embedded, retrieved and shown to the LLM.
Both carry:
text: the content.metadata: a dictionary such as{"file_name": "policy.pdf", "department": "hr", "year": 2025}.- an id:
doc_idfor documents,node_idfor nodes.
When a document is split, each node copies the document's metadata and records a relationship back to its source (node.ref_doc_id), plus links to the previous and next nodes.
You can create documents yourself from any data, for example rows from a database: Document(text=..., metadata={...}).
Metadata is powerful but has a cost: by default it is included in the text sent to the embedding model and to the LLM. That helps when it adds meaning (a title or section name) and hurts when it is noise (file sizes, internal ids). Control it per document:
excluded_embed_metadata_keys=["file_size"]: not used for embeddings.excluded_llm_metadata_keys=["internal_id"]: not shown to the LLM.
node.get_content(metadata_mode=MetadataMode.LLM) shows exactly what the LLM will see, which is the quickest way to debug it.
Good metadata to add: title, section, source URL, date, product, language and access level. It enables citations and filters later.
Example
from llama_index.core import Document
from llama_index.core.schema import MetadataMode
from llama_index.core.node_parser import SentenceSplitter
doc = Document(
text="Employees get 25 days of paid leave per year. Unused days expire in March.",
metadata={"title": "Leave policy", "department": "hr", "internal_id": "HR-17"},
excluded_llm_metadata_keys=["internal_id"], # hide from the LLM
excluded_embed_metadata_keys=["internal_id"], # and from embeddings
)
nodes = SentenceSplitter().get_nodes_from_documents([doc])
print(len(nodes)) # 1 (short text, one chunk)
print(nodes[0].metadata["title"], nodes[0].ref_doc_id == doc.doc_id) # Leave policy True
print(nodes[0].get_content(metadata_mode=MetadataMode.LLM))
# title: Leave policy
# department: hr
#
# Employees get 25 days of paid leave per year. Unused days expire in March.Remember
- Documents are whole sources; nodes are the chunks that get retrieved
- Nodes inherit metadata and link back to their document
- Exclude noisy metadata from embeddings or the LLM
Watch out for
- Stuffing long metadata (like full JSON records) into every node, which crowds out the real text in embeddings and prompts.