Firebase Admin for Swift is a server-side Swift package that provides a simple interface to interact with Firebase services using the Firebase Admin SDK.
This repository uses FirebaseAPI for gRPC-based Firebase service integration.
- ✅ Swift 6 compatible with strict concurrency
- 🔥 Firestore - Full CRUD operations, queries, transactions, and batch writes
- 🔐 Firebase Auth - User management and authentication
- 📨 Firebase Messaging - Push notifications via FCM
- ✅ AppCheck - App attestation and verification
- 🌍 Built-in types -
Timestamp,GeoPoint,DocumentReferencesupport - 📦 Codable - Native Swift Codable support with property wrappers
- 🔑 Flexible authentication - JSON file or environment variables
- Swift 6.2+
- macOS 15+ / iOS 18+
- Firebase project with service account credentials
dependencies: [
.package(url: "https://github.com/1amageek/FirebaseAdmin.git", branch: "main")
]Firebase Admin for Swift supports multiple ways to configure your service account credentials:
export FIREBASE_PROJECT_ID="your-project-id"
export FIREBASE_PRIVATE_KEY_ID="your-private-key-id"
export FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
export FIREBASE_CLIENT_EMAIL="[email protected]"
export FIREBASE_CLIENT_ID="123456789012345678901"import FirebaseApp
// Initialize from environment variables
try FirebaseApp.initializeFromEnvironment()import FirebaseApp
// Initialize from ServiceAccount.json file
FirebaseApp.initialize(fileName: "ServiceAccount")
// Or with custom path
let serviceAccount = try FirebaseApp.loadServiceAccount(from: "CustomServiceAccount")
FirebaseApp.initialize(serviceAccount: serviceAccount)Environment variables take precedence over JSON file:
import FirebaseApp
// Environment variables override JSON values
try await FirebaseApp.initializeFromConfiguration(jsonPath: "config/firebase.json")import Firestore
struct User: Codable, Equatable {
@DocumentID var id: String?
var name: String
var age: Int
var createdAt: Timestamp
var location: GeoPoint
}
let user = User(
name: "John Doe",
age: 30,
createdAt: Timestamp(),
location: GeoPoint(latitude: 37.7749, longitude: -122.4194)
)
// Create document
let ref = try Firestore.firestore()
.collection("users")
.document("user_id")
try await ref.setData(user)
// Read document
let snapshot = try await ref.getDocument(type: User.self)
print(snapshot?.name ?? "Not found")
// Query documents
let querySnapshot = try await Firestore.firestore()
.collection("users")
.where(field: "age", isGreaterThan: 25)
.getDocuments()
// Transaction
try await Firestore.firestore().runTransaction { transaction in
let snapshot = try await transaction.get(documentReference: ref)
if let user = try? snapshot.data(as: User.self) {
var updatedUser = user
updatedUser.age += 1
transaction.update(documentReference: ref, fields: ["age": updatedUser.age])
}
}
// Batch write
let batch = try Firestore.firestore().batch()
batch.setData(data: ["name": "Alice"], forDocument: ref)
batch.updateData(fields: ["age": 31], forDocument: ref)
try await batch.commit()import FirebaseAuth
let auth = FirebaseAuth()
// Get user by ID
let user = try await auth.getUser(uid: "user_id")
// List users
let users = try await auth.listUsers(maxResults: 100)
// Create custom token
let token = try await auth.createCustomToken(uid: "user_id", claims: ["admin": true])import FirebaseMessaging
let messaging = try FirebaseMessaging()
let message = Message(
token: "device_token",
notification: Notification(
title: "Hello",
body: "World"
)
)
let messageId = try await messaging.send(message: message)Represents a point in time with nanosecond precision:
let now = Timestamp()
let specific = Timestamp(seconds: 1609459200, nanos: 0)
let fromDate = Timestamp(date: Date())Represents geographical coordinates:
let location = GeoPoint(latitude: 37.7749, longitude: -122.4194)Represents a reference to a Firestore document:
let userRef = try Firestore.firestore().document("users/user_id")Automatically populates with the document ID when decoding:
struct User: Codable {
@DocumentID var id: String?
var name: String
}Note: @DocumentID is not saved as a field in Firestore. It's only populated during decoding.
Explicitly sets a field to null in Firestore:
struct User: Codable {
var name: String
@ExplicitNull var age: Int?
}
var user = User(name: "John", age: 30)
user.age = nil // This will set the field to null in FirestoreThis project uses Swift Testing framework (not XCTest).
# Run all tests (unit tests only, integration tests are disabled)
swift test
# Run specific test suite
swift test --filter FirebaseAppTests-
Unit Tests (✅ Enabled) - No Firebase credentials required
FirebaseAppTests- ServiceAccount initialization and configurationAppCheckTests- AppCheck functionality
-
Integration Tests (⏭️ Disabled by default) - Require actual Firebase credentials
FirestoreTests- Firestore path operationsDocumentTests- Document CRUD operationsWhereQueryTests- Query operationsRangeQueryTests- Range and composite queriesTransactionTests- Transaction operationsWriteBatchTests- Batch write operations
- Add valid
ServiceAccount.jsonto project root, or set environment variables - Remove
.disabled()from test suites inTests/FirestoreTests/ - Run tests:
swift testExample Vapor 4 integration:
// Package.swift
let package = Package(
name: "VaporApp",
platforms: [
.macOS(.v15)
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.76.0"),
.package(url: "https://github.com/1amageek/FirebaseAdmin.git", branch: "main"),
],
targets: [
.executableTarget(
name: "App",
dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "FirebaseApp", package: "FirebaseAdmin"),
.product(name: "Firestore", package: "FirebaseAdmin"),
.product(name: "FirebaseAuth", package: "FirebaseAdmin"),
]
)
]
)// configure.swift
import Vapor
import FirebaseApp
public func configure(_ app: Application) async throws {
// Initialize Firebase from environment variables
try FirebaseApp.initializeFromEnvironment()
// Register routes
try routes(app)
}-
Service Account JSON - Download from Firebase Console
- Go to Project Settings → Service Accounts
- Generate new private key
- Save as
ServiceAccount.json
-
Place the file in project root:
FirebaseAdmin/ ├── ServiceAccount.json (gitignored) ├── Package.swift └── ...
The .gitignore file already excludes:
ServiceAccount.json**/ServiceAccount.jsonfirebase-adminsdk-*.json.envfiles
For production deployment, use environment variables:
# Example .env file (gitignored)
FIREBASE_PROJECT_ID=my-project
FIREBASE_PRIVATE_KEY_ID=abc123...
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
FIREBASE_CLIENT_EMAIL=firebase-adminsdk@my-project.iam.gserviceaccount.com
FIREBASE_CLIENT_ID=123456789012345678901This project includes GitHub Actions workflows for automated testing.
To enable integration tests in CI, add the following secrets to your GitHub repository:
- Go to Settings → Secrets and variables → Actions
- Add the following repository secrets:
FIREBASE_PROJECT_IDFIREBASE_PRIVATE_KEY_IDFIREBASE_PRIVATE_KEYFIREBASE_CLIENT_EMAILFIREBASE_CLIENT_ID
- Test-only Firebase project: Use a separate Firebase project for CI/CD testing, not your production project
- Minimum permissions: Create a service account with only the permissions needed for tests
- Fork protection: Integration tests only run on pushes to main or PRs from the same repository (not from forks)
The CI pipeline runs two jobs:
-
Unit Tests: Run on every push and PR (no secrets required)
- Tests ServiceAccount initialization and configuration
- Tests AppCheck functionality
-
Integration Tests: Run only when secrets are available
- Automatically enables disabled integration test suites
- Tests Firestore operations with real Firebase credentials
- Only runs on main branch or same-repository PRs
# Manually run tests locally with environment variables
export FIREBASE_PROJECT_ID="test-project"
export FIREBASE_PRIVATE_KEY_ID="key-id"
export FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
export FIREBASE_CLIENT_EMAIL="[email protected]"
export FIREBASE_CLIENT_ID="123456789"
swift test- FirebaseApp - Core initialization and service account management
- Firestore - Firestore database operations with gRPC
- FirebaseAuth - User authentication and management
- FirebaseMessaging - Cloud messaging
- AppCheck - App attestation
This library is built with Swift 6 strict concurrency:
- Thread-safe with
Sendableconformance - Uses
Mutex<T>for synchronization - Async/await throughout
See LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
- FirebaseAPI - gRPC client generation
- Firebase Admin SDK
- Swift Configuration