# How to Use Docids to Reference Specific Documents in QMD Scripts

> Learn how to use docids for path-independent document retrieval in QMD scripts. This guide explains QMDs stable docid system for efficient CLI workflows.

- Repository: [Tobias Lütke/qmd](https://github.com/tobi/qmd)
- Tags: how-to-guide
- Published: 2026-02-16

---

**QMD assigns every indexed markdown file a short, stable docid derived from the first six characters of its content hash, enabling path-independent document retrieval in scripts and CLI workflows.**

When building automation around the `tobi/qmd` repository, you need a reliable way to use docids to reference specific documents in QMD scripts without hardcoding fragile file paths. Docids provide a content-addressable layer that remains stable even when files move within your collection.

## What Is a Docid in QMD?

A **docid** is a compact, hexadecimal identifier that represents a unique markdown document in the QMD index. Unlike file paths, which change when you reorganize directories, a docid remains constant as long as the file’s content does not change.

QMD generates this identifier by computing a SHA-256 hash of the file’s raw content and extracting the first six characters. This produces a short string like `#dc5590` that is easy to copy, paste, and embed in scripts.

## How Docids Are Generated and Stored

The indexing logic lives in [`src/store.ts`](https://github.com/tobi/qmd/blob/main/src/store.ts). When a file is processed, the `indexFile()` function computes the content hash and derives the docid:

```typescript
// src/store.ts (lines 1497-1503)
const hash = crypto.createHash('sha256').update(content).digest('hex');
const docid = hash.substring(0, 6);  // First six characters

```

This mapping is stored in two places:

- **SQLite FTS5 table**: The `docid` column links the short hash to the full-text index.
- **In-memory Map**: An internal `docidMap` provides O(1) lookups during CLI operations.

## Normalizing Docid Input

Users may reference a docid in various formats: `#abc123`, `"abc123"`, or simply `abc123`. To handle this, [`src/store.ts`](https://github.com/tobi/qmd/blob/main/src/store.ts) exports `normalizeDocid()`:

```typescript
// src/store.ts (lines 1501-1507)
export function normalizeDocid(input: string): string {
  let cleaned = input.trim();
  if (cleaned.startsWith('#')) cleaned = cleaned.slice(1);
  if ((cleaned.startsWith('"') && cleaned.endsWith('"')) ||
      (cleaned.startsWith("'") && cleaned.endsWith("'"))) {
    cleaned = cleaned.slice(1, -1);
  }
  return cleaned.toLowerCase();
}

```

This ensures that all variants are treated uniformly before lookup.

## Retrieving Documents by Docid

The core lookup routine `findDocumentByDocid()` (also in [`src/store.ts`](https://github.com/tobi/qmd/blob/main/src/store.ts)) receives a normalized short hash, searches the index for a matching row, and returns the original file path and full hash:

```typescript
// src/store.ts (lines 1530-1540)
export function findDocumentByDocid(
  db: Database,
  shortHash: string
): { filepath: string; hash: string } | null {
  const stmt = db.prepare(
    "SELECT filepath, hash FROM documents WHERE docid = ?"
  );
  return stmt.get(shortHash) || null;
}

```

All higher-level operations—search, CLI, and MCP—call this helper when the user supplies a docid.

## Using Docids in CLI Workflows

### Search Output

Every search format (`--json`, `--csv`, `--md`, `--files`, default) appends the docid to each result. In [`src/qmd.ts`](https://github.com/tobi/qmd/blob/main/src/qmd.ts), the formatter prepends `#` to the short hash:

```bash

# Search and see docids in JSON output

qmd search "quarterly review" --json

# Output includes: {"docid":"#dc5590", "title":"Q3 Review", ...}

```

### Retrieving Documents

The `qmd get` command accepts either a file path or a docid. Internally, it runs a fuzzy-lookup flow that first tries a direct path, then falls back to `findDocumentByDocid`:

```bash

# Retrieve by docid

qmd get "#dc5590"

# Or without the hash symbol

qmd get "dc5590"

```

This is implemented in [`src/qmd.ts`](https://github.com/tobi/qmd/blob/main/src/qmd.ts) (lines 748-754).

## Programmatic Access in Scripts

When writing QMD scripts in TypeScript or JavaScript, import the store utilities to resolve docids manually:

```typescript
import { findDocumentByDocid, normalizeDocid } from "./src/store.js";

// Resolve a docid to a file path
const { filepath, hash } = findDocumentByDocid(db, "dc5590");
console.log(`Document ${hash.slice(0,6)} located at: ${filepath}`);

```

Use `normalizeDocid()` to sanitize user input before lookup:

```typescript
function resolveUserInput(input: string) {
  const docid = normalizeDocid(input);          // Handles "#dc5590" or '"dc5590"'
  const match = findDocumentByDocid(db, docid);
  return match?.filepath ?? null;
}

```

## MCP and Chat-Based Interfaces

For AI assistants and chat-based tools, the MCP (Model Context Protocol) server exposes docids in document listings. The "get" action accepts a docid prefixed with `#`:

```typescript
// src/mcp.ts (lines 361-368)
// MCP tools can call:
// { action: "get", docid: "#dc5590" }

```

This allows LLM-based scripts to reference documents without needing filesystem context.

## Why Use Docids?

- **Path-independent references**: Store `#abc123` instead of fragile relative paths like [`../notes/2024/q3.md`](https://github.com/tobi/qmd/blob/main/../notes/2024/q3.md). Moving files within the collection does not break scripts.
- **Stability across clones**: As long as content remains identical, the docid is consistent across different machines and git clones.
- **Compact identifiers**: Six-character strings are easier to embed in URLs, markdown links, and chat messages than full SHA-256 hashes or long file paths.

## Summary

- A **docid** is the first six characters of a file’s SHA-256 content hash, providing a stable, path-independent identifier.
- Docids are generated in [`src/store.ts`](https://github.com/tobi/qmd/blob/main/src/store.ts) and stored in the SQLite FTS5 index alongside file paths.
- Use `normalizeDocid()` to sanitize user input and `findDocumentByDocid()` to resolve short hashes to full paths programmatically.
- The CLI commands `qmd search` and `qmd get` both support docid references, outputting and accepting the `#abc123` format.
- MCP integrations allow AI assistants to reference documents by docid in chat-based workflows.

## Frequently Asked Questions

### What happens if two files have the same first six hash characters?

While statistically unlikely due to the uniform distribution of SHA-256 hashes, `findDocumentByDocid()` in [`src/store.ts`](https://github.com/tobi/qmd/blob/main/src/store.ts) returns the first match found in the database. In practice, collisions are rare enough that six characters provide sufficient uniqueness for most document collections. If absolute uniqueness is required, use the full 64-character hash stored in the `hash` column.

### Can I use docids in markdown links?

Yes. Because docids are stable content identifiers, you can embed them in markdown links or documentation. When processed by QMD-aware tools, `#abc123` can be resolved to the current file path via `findDocumentByDocid()`. For external tools, you may need to implement a lookup layer that queries the QMD SQLite database directly.

### How do I find the docid for a specific file?

Run `qmd search` with a query that matches the file’s content or title, and include the `--json` or `--md` flag. The output will include the `docid` field (e.g., `"#dc5590"`). Alternatively, if you know part of the file name, search for it and extract the docid from the results. There is currently no direct "show docid for path" command, but you can script this using the `findDocumentByDocid` logic in reverse by querying the database for the path and reading the `docid` column.

### Are docids stable when I edit a file?

No. Because docids are derived from the SHA-256 hash of file content, any modification—even a single character change—will generate a new hash and therefore a new docid. This is by design: docids identify specific content versions, not logical documents over time. If you need stable references across edits, you must maintain a separate mapping layer or use the file path directly.