Skip to main content
GRDB provides a set of protocols that allow Swift types to interact with database rows. These protocols form the foundation of GRDB’s record layer, enabling you to fetch data from the database and persist changes back to it.

The Record Protocols

GRDB defines three main record protocols:

FetchableRecord

Decode database rows into Swift types

PersistableRecord

Encode Swift types into database rows

Codable Records

Leverage Swift’s Codable for automatic conformance

Protocol Hierarchy

The record protocols build on each other:
TableRecord
    ├── FetchableRecord (read from database)
    └── EncodableRecord
            ├── PersistableRecord (write to database, immutable)
            └── MutablePersistableRecord (write to database, mutable)

FetchableRecord

Types that conform to FetchableRecord can be decoded from database rows:
struct Player: FetchableRecord {
    var id: Int64
    var name: String
    var score: Int
    
    init(row: Row) throws {
        id = row["id"]
        name = row["name"]
        score = row["score"]
    }
}

// Fetch players from the database
let players = try Player.fetchAll(db, sql: "SELECT * FROM player")

PersistableRecord & MutablePersistableRecord

Types that conform to PersistableRecord or MutablePersistableRecord can be saved to the database:
struct Player: MutablePersistableRecord {
    var id: Int64?
    var name: String
    var score: Int
    
    func encode(to container: inout PersistenceContainer) {
        container["id"] = id
        container["name"] = name
        container["score"] = score
    }
    
    mutating func didInsert(_ inserted: InsertionSuccess) {
        id = inserted.rowID
    }
}

// Insert a new player
var player = Player(id: nil, name: "Arthur", score: 100)
try player.insert(db)
print(player.id) // The auto-incremented id

Choosing Between Protocols

Use FetchableRecord alone when you only need to read data from the database:
  • Read-only views or computed data
  • Data from complex queries or joins
  • Immutable reference data
struct PlayerStats: FetchableRecord {
    var name: String
    var totalScore: Int
    var gamesPlayed: Int
    
    init(row: Row) throws {
        name = row["name"]
        totalScore = row["totalScore"]
        gamesPlayed = row["gamesPlayed"]
    }
}
Use MutablePersistableRecord for struct-based records with auto-incremented primary keys:
  • Records are value types (structs)
  • Need to capture auto-incremented IDs after insertion
  • Prefer Swift’s value semantics
struct Player: MutablePersistableRecord {
    var id: Int64?
    var name: String
    
    mutating func didInsert(_ inserted: InsertionSuccess) {
        id = inserted.rowID
    }
}
Use PersistableRecord for class-based records or immutable structs:
  • Records are reference types (classes)
  • No need to mutate on insertion
  • Primary keys are set before insertion
class Player: PersistableRecord {
    var id: Int64?
    var name: String
    
    func didInsert(_ inserted: InsertionSuccess) {
        id = inserted.rowID
    }
}
Add TableRecord conformance to enable query interface methods:
struct Player: FetchableRecord, MutablePersistableRecord, TableRecord {
    static let databaseTableName = "player"
    // ...
}

// Now you can use query interface methods
let player = try Player.filter(Column("id") == 42).fetchOne(db)
let highScorers = try Player.filter(Column("score") > 1000).fetchAll(db)

Codable Integration

The simplest way to adopt record protocols is through Swift’s Codable:
struct Player: Codable {
    var id: Int64?
    var name: String
    var score: Int
}

// Automatic conformance when you add the protocols
extension Player: FetchableRecord, MutablePersistableRecord {
    static let databaseTableName = "player"
    
    mutating func didInsert(_ inserted: InsertionSuccess) {
        id = inserted.rowID
    }
}
When your type conforms to Codable, GRDB automatically provides implementations for init(row:) and encode(to:). See Codable Records for more details.

Persistence Methods

Records that conform to MutablePersistableRecord or PersistableRecord get persistence methods:
insert(_:)
method
Inserts a new record into the database
var player = Player(id: nil, name: "Arthur", score: 100)
try player.insert(db)
update(_:)
method
Updates an existing record based on its primary key
player.score = 200
try player.update(db)
save(_:)
method
Inserts or updates depending on whether the record exists
try player.save(db) // Inserts if new, updates if exists
delete(_:)
method
Deletes the record from the database
try player.delete(db)

Best Practices

Use Codable when possible: If your Swift type’s properties map directly to database columns, use Codable for automatic conformance. This reduces boilerplate and keeps your code maintainable.
Prefer structs with MutablePersistableRecord: Swift structs with value semantics work well with MutablePersistableRecord. Use the didInsert(_:) callback to capture auto-incremented IDs.
The Record class is legacy: GRDB historically provided a Record base class, but this approach is no longer recommended. Prefer protocol conformance with structs instead.
Thread safety: Record instances are not thread-safe. Don’t share record instances across threads. Always perform database operations within read or write closures.

Next Steps

FetchableRecord

Learn how to fetch records from the database

PersistableRecord

Learn how to persist records to the database

Codable Records

Use Codable for automatic protocol conformance

Build docs developers (and LLMs) love