dgraph-io/badger · error

ErrInvalidKey

ErrInvalidKey

Error message

Key is using a reserved !badger! prefix

What it means

ErrInvalidKey is returned when a user key uses the reserved '!badger!' prefix, which Badger keeps for internal keys (like !badger!head). Txn.modify rejects any key with that prefix (txn.go:360) to protect internal metadata.

Solutions

  1. Rename user keys so they don't start with '!badger!' (e.g. add an app prefix like 'app:')
  2. Sanitize/validate incoming keys before writing
  3. If you need internal data access, use the documented APIs instead of raw keys

Example fix

// before
db.Update(func(txn *Txn) error {
    return txn.SetEntry(NewEntry([]byte("!badger!head"), val)) // ErrInvalidKey
})
// after
db.Update(func(txn *Txn) error {
    return txn.SetEntry(NewEntry([]byte("app!badger!head"), val))
})
Defensive patterns

Strategy: validation

Validate before calling

if bytes.HasPrefix(key, []byte("!badger!")) {
    return fmt.Errorf("key %q uses reserved prefix", key)
}

Type guard

func isReservedKey(key []byte) bool {
    return bytes.HasPrefix(key, []byte("!badger!"))
}

Try / catch

if err := txn.Set(key, val); err != nil {
    if errors.Is(err, badger.ErrInvalidKey) {
        return fmt.Errorf("rename key %q: uses reserved !badger! prefix", key)
    }
    return err
}

Prevention

When it happens

Trigger: txn.Set/NewEntry with keys like "!badger!head" or "!badger!" (db_test.go:1124/1127); copying external data whose keys happen to start with !badger!. Note "!badger" (no trailing '!') is allowed.

Common situations: Importing datasets from other stores, generating keys from user input that collides with the prefix, probing internal keys from user code.

Related errors


AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05). Data as JSON: /api/errors/2d7db7a2ffa0c449. Report an issue: GitHub.

Appendix: source

Thrown at errors.go:44

	// ErrTxnTooBig is returned if too many writes are fit into a single transaction.
	ErrTxnTooBig = stderrors.New("Txn is too big to fit into one request")

	// ErrConflict is returned when a transaction conflicts with another transaction. This can
	// happen if the read rows had been updated concurrently by another transaction.
	ErrConflict = stderrors.New("Transaction Conflict. Please retry")

	// ErrReadOnlyTxn is returned if an update function is called on a read-only transaction.
	ErrReadOnlyTxn = stderrors.New("No sets or deletes are allowed in a read-only transaction")

	// ErrDiscardedTxn is returned if a previously discarded transaction is reused.
	ErrDiscardedTxn = stderrors.New("This transaction has been discarded. Create a new one")

	// ErrEmptyKey is returned if an empty key is passed on an update function.
	ErrEmptyKey = stderrors.New("Key cannot be empty")

	// ErrInvalidKey is returned if the key has a special !badger! prefix,
	// reserved for internal usage.
	ErrInvalidKey = stderrors.New("Key is using a reserved !badger! prefix")

	// ErrBannedKey is returned if the read/write key belongs to any banned namespace.
	ErrBannedKey = stderrors.New("Key is using the banned prefix")

	// ErrThresholdZero is returned if threshold is set to zero, and value log GC is called.
	// In such a case, GC can't be run.
	ErrThresholdZero = stderrors.New(
		"Value log GC can't run because threshold is set to zero")

	// ErrNoRewrite is returned if a call for value log GC doesn't result in a log file rewrite.
	ErrNoRewrite = stderrors.New(
		"Value log GC attempt didn't result in any cleanup")

	// ErrRejected is returned if a value log GC is called either while another GC is running, or
	// after DB::Close has been called.
	ErrRejected = stderrors.New("Value log GC request rejected")

	// ErrInvalidRequest is returned if the user request is invalid.

View on GitHub (pinned to 2a001d466f)