dgraph-io/dgraph · error

Schema change not allowed from %s to %s

Error message

Schema change not allowed from %s to %s

What it means

Once a predicate has data, Dgraph forbids changing its type to or from the password type (and more generally disallowed scalar-type changes) because existing postings were encoded with the old type and cannot be reinterpreted. checkSchema compares the existing schema type t with the requested type and errors when the change involves pb.Posting_PASSWORD and the types differ.

Source

Thrown at worker/mutation.go:437

		ctx := context.WithValue(context.Background(), schema.IsWrite, false)
		prevSchema, _ := schema.State().Get(ctx, s.Predicate)
		if err := validateSchemaForUnique(prevSchema, s); err != nil {
			return err
		}
	}

	t, err := schema.State().TypeOf(s.Predicate)
	if err != nil {
		// No schema previously defined, so no need to do checks about schema conversions.
		return nil
	}

	// schema was defined already
	switch {
	case t.IsScalar() && (t.Enum() == pb.Posting_PASSWORD || s.ValueType == pb.Posting_PASSWORD):
		// can't change password -> x, x -> password
		if t.Enum() != s.ValueType {
			return errors.Errorf("Schema change not allowed from %s to %s",
				t.Enum(), typ.Enum())
		}

	case t.IsScalar() == typ.IsScalar():
		// If old type was list and new type is non-list, we don't allow it until user
		// has data.
		if schema.State().IsList(s.Predicate) && !s.List && hasEdges(s.Predicate, math.MaxUint64) {
			return errors.Errorf("Schema change not allowed from [%s] => %s without"+
				" deleting pred: %s", t.Name(), typ.Name(), x.ParseAttr(s.Predicate))
		}

	default:
		// uid => scalar or scalar => uid. Check that there shouldn't be any data.
		if hasEdges(s.Predicate, math.MaxUint64) {
			return errors.Errorf("Schema change not allowed from scalar to uid or vice versa"+
				" while there is data for pred: %s", x.ParseAttr(s.Predicate))
		}
	}

View on GitHub (pinned to 759e242be6)

Solutions

  1. Drop the predicate first (drop all data for the predicate / alter to delete), then apply the new type.
  2. Create a new predicate with the desired type and migrate data with an upsert/DQL rewrite, then drop the old one.
  3. If the types truly match (both password), confirm ValueType equals the existing t.Enum() so no change is requested.
  4. Do type-change planning offline: read schema.State() and diff before proposing the mutation.

Example fix

// before
userpass: string @upsert .   // was previously: userpass: password
// after
drop pred userpass first, then:
userpass: password .
Defensive patterns

Strategy: validation

Validate before calling

func validateNoPasswordChange(current pb.Posting_ValType, next pb.Posting_ValType, hasData bool) error {
    if hasData && (current == pb.Posting_PASSWORD) != (next == pb.Posting_PASSWORD) {
        return fmt.Errorf("cannot change type involving password with existing data")
    }
    return nil
}

Try / catch

err := dgraph.Alter(ctx, op)
if err != nil && strings.Contains(err.Error(), "Schema change not allowed") {
    // drop pred / migrate to new pred, then re-apply schema
}

Prevention

When it happens

Trigger: Altering a predicate that already has data from password to another scalar type (e.g. 'password: string' where it was 'password: password'), or into password from another type, via ALTER while old postings exist.

Common situations: Renaming or retyping a credential field during refactors; switching from Dgraph's password type to hashed strings stored as plain string; schema cleanups on production databases with existing users.

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/8480c4aa8534753e. Report an issue: GitHub.