Go GORM Foreign Key Constraint Violation on Insert

A foreign key constraint violation in GORM during an insert almost always means you’re trying to save a record that references a parent row which doesn’t exist yet — or one that GORM thinks exists because of how Go’s zero values interact with foreign key fields.

The Problem

You try to insert a record with an associated foreign key, and instead of succeeding, GORM returns an error like:

ERROR: insert or update on table "orders" violates foreign key constraint "fk_orders_customer"
DETAIL: Key (customer_id)=(0) is not present in table "customers".

The referenced ID in the error is often 0, even though you never intentionally set it to zero — which is usually the first clue pointing to what’s actually going wrong.

Why It Happens

In Go, an unset integer field defaults to its zero value, and GORM doesn’t always distinguish “I deliberately want this to reference ID 0” from “I forgot to set this field.” The most common causes are:

  • The parent struct (e.g. Customer) was never actually saved before the child struct (e.g. Order) tried to reference it, so its ID is still its zero value at the time of the insert
  • You’re setting the foreign key field directly (like CustomerID) instead of assigning the related struct itself, and a typo or unset variable left it at its zero value
  • You’re relying on GORM’s auto-save-associations behavior, but the association wasn’t populated correctly on the struct before calling Create, so GORM has nothing to save first
  • The referenced parent record genuinely was deleted or never existed with that ID, often from stale test data or an ID copied from a different environment

The Fix

First, confirm the parent record actually exists and has the ID you expect it to have, right before the insert that’s failing:

var customer Customer
result := db.First(&customer, expectedID)
fmt.Println(customer.ID, result.Error)

If you’re relying on GORM to save an associated struct automatically, make sure the association is actually populated as a struct, not just an ID field, and that the parent is created first, or as part of the same call:

order := Order{
    Customer: customer, // full struct, so GORM can save/reference it correctly
    Amount:   99.90,
}
db.Create(&order)

If you’re intentionally setting the foreign key ID manually instead of relying on the association, make sure that ID is actually set correctly and isn’t accidentally left at its zero value due to struct initialization order:

order := Order{
    CustomerID: customer.ID, // make sure customer.ID is populated by this point
    Amount:     99.90,
}
db.Create(&order)

It also helps to enable GORM’s debug mode temporarily, so you can see the exact SQL being generated and confirm the actual value being inserted for the foreign key column, rather than assuming based on your Go code alone:

db.Debug().Create(&order)

Still Not Working?

If the parent record definitely exists with the correct ID and the foreign key value is definitely being set correctly, check whether the two records are being created inside separate, uncommitted transactions — if the parent insert hasn’t been committed yet when the child insert runs, the database won’t be able to see it, even though your Go code executed the parent’s Create call first. Wrapping both operations in a single transaction ensures the parent is visible to the child insert within the same transactional context:

err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&customer).Error; err != nil {
        return err
    }
    order := Order{CustomerID: customer.ID, Amount: 99.90}
    return tx.Create(&order).Error
})

It’s also worth checking whether the foreign key constraint itself references the correct column and table. A migration that was written against an earlier version of the schema, and never updated after a table or column rename, can leave a foreign key constraint pointing at something that no longer matches your current struct definitions — in that case, even a perfectly correct insert will fail, because the constraint itself is stale relative to the rest of your codebase. Running a quick check directly against the database’s schema catalog can confirm whether the constraint definition still matches what you expect:

SELECT conname, confrelid::regclass, conrelid::regclass
FROM pg_constraint
WHERE contype = 'f' AND conname = 'fk_orders_customer';

If the constraint is referencing an unexpected table or column, the fix is a new migration correcting the constraint definition, not a change to your Go insert logic at all.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *