
A toolkit for SQLite databases, with a focus on application development
Proudly serving the community since 2015
Latest release: June 18, 2026 • version 7.11.1 • CHANGELOG • Migrating From GRDB 6 to GRDB 7
Requirements: iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 7.0+ • SQLite 3.20.0+ • Swift 6.1+ / Xcode 16.3+
Contact:
- Release announcements and usage tips: follow @[email protected] on Mastodon.
- Report bugs in a Github issue. Make sure you check the existing issues first.
- A question? Looking for advice? Do you wonder how to contribute? Fancy a chat? Go to the GitHub discussions, or the GRDB forums.
What is GRDB?
Use this library to save your application’s permanent data into SQLite databases. It comes with built-in tools that address common needs:
SQL Generation
Enhance your application models with persistence and fetching methods, so that you don't have to deal with SQL and raw database rows when you don't want to.
Database Observation
Get notifications when database values are modified.
Robust Concurrency
Multi-threaded applications can efficiently use their databases, including WAL databases that support concurrent reads and writes.
Migrations
Evolve the schema of your database as you ship new versions of your application.
Leverage your SQLite skills
Not all developers need advanced SQLite features. But when you do, GRDB is as sharp as you want it to be. Come with your SQL and SQLite skills, or learn new ones as you go!
Usage • Documentation • Installation • FAQ
Usage
Start using the database in four steps
import GRDB
// 1. Open a database connection
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")
// 2. Define the database schema
try dbQueue.write { db in
try db.create(table: "player") { t in
t.primaryKey("id", .text)
t.column("name", .text).notNull()
t.column("score", .integer).notNull()
}
}
// 3. Define a record type
struct Player: Codable, Identifiable, FetchableRecord, PersistableRecord {
var id: String
var name: String
var score: Int
enum Columns {
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
}
}
// 4. Write and read in the database
try dbQueue.write { db in
try Player(id: "1", name: "Arthur", score: 100).insert(db)
try Player(id: "2", name: "Barbara", score: 1000).insert(db)
}
try dbQueue.read { db in
let player = try Player.find(db, id: "1")
let bestPlayers = try Player
.order(\.score.desc)
.limit(10)
.fetchAll(db)
}
Access to raw SQL
try dbQueue.write { db in
try db.execute(sql: """
CREATE TABLE player (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
score INT NOT NULL)
""")
try db.execute(sql: """
INSERT INTO player (id, name, score)
VALUES (?, ?, ?)
""", arguments: ["1", "Arthur", 100])
// Avoid SQL injection with SQL interpolation
let id = "2"
let name = "O'Brien"
let score = 1000
try db.execute(literal: """
INSERT INTO player (id, name, score)
VALUES (\(id), \(name), \(score))
""")
}
Access to raw database rows and values
try dbQueue.read { db in
// Fetch database rows
let rows = try Row.fetchCursor(db, sql: "SELECT * FROM player")
while let row = try rows.next() {
let id: String = row["id"]
let name: String = row["name"]
let score: Int = row["score"]
}
// Fetch values
let playerCount = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM player")! // Int
let playerNames = try String.fetchAll(db, sql: "SELECT name FROM player") // [String]
}
let playerCount = try dbQueue.read { db in
try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM player")!
}
See Fetch Queries
Database model types aka "records"
struct Player: Codable, Identifiable, FetchableRecord, PersistableRecord {
var id: String
var name: String
var score: Int
enum Columns {
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
}
}
try dbQueue.write { db in
// Create database table
try db.create(table: "player") { t in
t.primaryKey("id", .text)
t.column("name", .text).notNull()
t.column("score", .integer).notNull()
}
// Insert a record
var player = Player(id: "1", name: "Arthur", score: 100)
try player.insert(db)
// Update a record
player.score += 10
try player.update(db)
try player.updateChanges { $0.score += 10 }
// Delete a record
try player.delete(db)
}
See Records
Query the database with the Swift query interface
try dbQueue.read { db in
// Player
let player = try Player.find(db, id: "1")
// Player?
let arthur = try Player.filter { $0.name == "Arthur" }.fetchOne(db)
// [Player]
let bestPlayers = try Player.order(\.score.desc).limit(10).fetchAll(db)
// Int
let playerCount = try Player.fetchCount(db)
// SQL is always welcome
let players = try Player.fetchAll(db, sql: "SELECT * FROM player")
}
See the Query Interface
Database changes notifications
// Define the observed value
let observation = ValueObservation.tracking { db in
try Player.fetchAll(db)
}
// Start observation
let cancellable = observation.start(
in: dbQueue,
onError: { error in ... },
onChange: { (players: [Player]) in print("Fresh players: \(players)") })
Ready-made support for Combine and RxSwift:
// Swift concurrency
for try await players in observation.values(in: dbQueue) {
print("Fresh players: \(players)")
}
// Combine
let cancellable = observation.publisher(in: dbQueue).sink(
receiveCompletion: { completion in ... },
receiveValue: { (players: [Player]) in print("Fresh players: \(players)") })
// RxSwift
let disposable = observation.rx.observe(in: dbQueue).subscribe(
onNext: { (players: [Player]) in print("Fresh players: \(players)") },
onError: { error in ... })
See [Database Observation], [Combine Support], [RxGRDB].
Documentation
GRDB runs on top of SQLite: you should get familiar with the SQLite FAQ. For general and detailed information, jump to the SQLite Documentation.
Demo Applications & Frequently Asked Questions
- [Demo Applications]
- [FAQ]
Reference
Getting Started
- Installation
- [Database Connections]: Connect to SQLite databases
SQLite and SQL
- SQLite API: The low-level SQLite API • executing updates • fetch queries • [SQL Interpolation]