SQLite vs Core Data: How Core Data Uses SQLite (Tested on macOS)

Core Data can store an app’s data in SQLite itself, so Core Data versus SQLite isn’t a choice between two databases. It’s a choice between using SQLite through Apple’s object layer and using it directly. In this article I’ll walk you through what Core Data writes into SQLite, from a small store I built and opened in the sqlite3 shell, and when to use each one.

TLDR

  • Core Data is Apple’s object-persistence framework. SQLite is a database engine, and it’s one of the stores Core Data can write to.
  • Use Core Data when you want to work with Swift objects and sync them through CloudKit, with little SQL.
  • Use SQLite directly when you want full SQL over a schema you control. You also get a file that other platforms can read.
  • A Core Data SQLite file uses Core Data’s own table and column names, and stores dates as seconds since 2001.

What is SQLite?

SQLite is a relational database engine that keeps a whole database in one file and runs inside your app. You work with it in SQL: you design the tables and write the queries yourself. It isn’t tied to Apple platforms, so other apps and tools can open the same file.

What is Core Data?

Core Data is Apple’s framework for saving your app’s objects. Apple describes it as a way to “Persist or cache data on a single device, or sync data to multiple devices with CloudKit” (Apple: Core Data). It lists “The SQLite database store type” among its store types (NSSQLiteStoreType). So Core Data is an abstraction layer, and SQLite can be the database underneath it.

SQLite vs Core Data at a glance

The two work at different levels, so the comparison is about what you write and what you get:

SQLiteCore Data
What it isA database engineAn object-persistence framework
You work withTables, rows and SQLSwift or Objective-C objects
SchemaYoursCore Data’s, generated from your model
PlatformsNot tied to one vendorApple platforms

What Core Data writes into SQLite

To show you the difference, I will build a tiny Core Data app that saves three notes and fetches one. Then I’ll open the store with the sqlite3 shell. Each subsection below looks at one part of the file.

The Core Data side

I defined the model in code instead of in an Xcode data model file. The script saves three notes to an SQLite store and fetches one with a predicate:

import CoreData
import Foundation

// A Core Data model defined in code (an Xcode .xcdatamodeld file would normally hold this)
let note = NSEntityDescription()
note.name = "Note"
note.managedObjectClassName = NSStringFromClass(NSManagedObject.self)
let title = NSAttributeDescription()
title.name = "title"; title.attributeType = .stringAttributeType; title.isOptional = false
let created = NSAttributeDescription()
created.name = "created"; created.attributeType = .dateAttributeType; created.isOptional = false
note.properties = [title, created]
let model = NSManagedObjectModel()
model.entities = [note]

// Persist to an SQLite store file
let url = URL(fileURLWithPath: "Notes.sqlite")
let container = NSPersistentContainer(name: "Notes", managedObjectModel: model)
let description = NSPersistentStoreDescription(url: url)
description.type = NSSQLiteStoreType
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { _, error in if let error = error { fatalError("\(error)") } }

let context = container.viewContext
for t in ["Buy milk", "Write docs", "Ship app"] {
    let obj = NSManagedObject(entity: note, insertInto: context)
    obj.setValue(t, forKey: "title")
    obj.setValue(Date(timeIntervalSince1970: 1_791_297_015), forKey: "created")
}
try context.save()

let request = NSFetchRequest<NSManagedObject>(entityName: "Note")
request.predicate = NSPredicate(format: "title BEGINSWITH %@", "W")
request.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
let results = try context.fetch(request)
print("fetched:", results.map { $0.value(forKey: "title") as! String })
print("created as timeIntervalSinceReferenceDate:", Int(Date(timeIntervalSince1970: 1_791_297_015).timeIntervalSinceReferenceDate))
$ swift coredata_demo.swift
fetched: ["Write docs"]
created as timeIntervalSinceReferenceDate: 812989815

The store is an ordinary SQLite file

Core Data put the store in WAL mode. Its -wal and -shm files were still next to the store after the script exited:

$ ls Notes.sqlite*
Notes.sqlite
Notes.sqlite-shm
Notes.sqlite-wal
$ sqlite3 Notes.sqlite "PRAGMA journal_mode;"
wal

Core Data names the tables and columns

The Note entity became the table ZNOTE, and its attributes became ZTITLE and ZCREATED. Core Data also added its own bookkeeping columns and tables:

$ sqlite3 Notes.sqlite ".tables"
ZNOTE         Z_METADATA    Z_MODELCACHE  Z_PRIMARYKEY
$ sqlite3 Notes.sqlite ".schema ZNOTE"
CREATE TABLE ZNOTE ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZCREATED TIMESTAMP, ZTITLE VARCHAR );
$ sqlite3 -header -column Notes.sqlite "SELECT ZTITLE, ZCREATED FROM ZNOTE ORDER BY ZTITLE;"
ZTITLE      ZCREATED
----------  ---------
Buy milk    812989815
Ship app    812989815
Write docs  812989815

Dates are seconds since 2001

The date is stored as seconds since 1 January 2001, Apple’s reference date. The Swift script printed the same date’s timeIntervalSinceReferenceDate as 812989815, the value in ZCREATED. A query that reads the file directly has to know that.

When to use SQLite and when to use Core Data

The run above shows the trade-off. Core Data’s file belongs to Core Data, and SQLite’s file belongs to you:

  • Core Data: an Apple-only app built around objects that needs undo or iCloud sync. Apple lists object mapping “without administering a database directly” and “mechanisms for versioning your data model and migrating user data as your app evolves” (Apple: Core Data).
  • SQLite directly: data you share with other platforms, or complex SQL reporting. See SQLite on iOS.
  • Both in one app: possible, but keep each database file owned by one of them.

Conclusion

Core Data and SQLite aren’t rivals. Core Data is a layer that can store your objects in SQLite, and it brings its own schema and date format to that file. Choose Core Data for an Apple-only app that wants objects and sync. Choose SQLite directly when you want SQL and a file other tools can read.

Further reading

Tested with Swift 6.3.3 (Command Line Tools, no Xcode), SQLite 3.51.0 and macOS 26.6.2 on 7 October 2026.