iOS + Apollo
How to use the DataDome SDK with the Apollo Client for GraphQL
Upgrading from 3.8.x or earlier?This guide covers a fresh v4 integration. For a step-by-step
upgrade from aDataDomeSDK-based version, seeMIGRATION.md.
Still integrating an older version of this SDK?The v3.x documentation has been moved to this page.
Requirements
| iOS | 15.0+ (raised from iOS 12 in earlier versions) |
| Xcode | 26.4+ — the first version that supports Swift Package Manager package traits, which DataDomeApollo uses to select the Apollo major |
| Integration method | Swift Package Manager only — CocoaPods support was removed in 4.0.0 |
| Apollo iOS | v1 (1.x) or v2 (2.x) — pick one and enable the matching trait |
| DataDome client-side key | Find it on your DataDome Dashboard https://app.datadome.co/dashboard/management/integrations?created=2 |
CocoaPods (
pod "DataDomeApollo") was the legacy installation path. It is no longer supported;
integrate via Swift Package Manager.
Choosing your Apollo iOS major and adding the package
DataDomeApollo ships support for both Apollo majors in a single package and selects between them at
build time using two traits: ApolloV1 (enabled by default) and ApolloV2. SwiftPM resolves a
single Apollo version per build, so enable the trait that matches the Apollo major your app
depends on. A mismatch is a compile error, not a silent failure.
Apollo v1 (default — no extra configuration)
Add the package to your Package.swift dependencies:
.package(url: "https://github.com/DataDome/datadome-apollo-package", from: "4.0.0")The ApolloV1 trait is enabled by default, so nothing else is required. Existing Apollo v1
integrators get the v1 API as before.
Apollo v2 (opt in to the ApolloV2 trait)
ApolloV2 trait)Enable the ApolloV2 trait, and make sure your app pins apollo-ios to a 2.x version:
.package(url: "https://github.com/DataDome/datadome-apollo-package", from: "4.0.0", traits: ["ApolloV2"])In Xcode, enable the
ApolloV2trait from the package dependency's trait settings (Project →
Package Dependencies → DataDomeApollo → Traits) rather than editing a manifest.
Whichever major you use, add the DataDomeApollo library to your app target.
Configure your DataDome client-side key
DataDomeApollo validates responses through a DataDome instance that you create and
inject. Configure it programmatically or from your Info.plist.
Option A — Programmatic
import CoreDataDome
let dataDomeConfiguration = DataDomeConfiguration(clientKey: "YOUR_CLIENT_SIDE_KEY")
let dataDome = DataDome(configuration: dataDomeConfiguration)Create the DataDome instance once and reuse it for the lifetime of your Apollo client.
Option B — Info.plist
Add a DataDome dictionary with your ClientSideKey (and, optionally, a Domain):
<key>DataDome</key>
<dict>
<key>ClientSideKey</key>
<string>YOUR_CLIENT_SIDE_KEY</string>
<!-- Optional -->
<key>Domain</key>
<string>https://your-domain.com</string>
</dict>Then build the instance from the bundle:
import CoreDataDome
let dataDomeConfiguration = try DataDomeConfiguration.configurationFromBundle()
let dataDome = DataDome(configuration: dataDomeConfiguration)
DataDomeConfiguration.configurationFromBundle()throws if theDataDomedictionary or itsClientSideKeyentry is missing — handle the error, or use the programmatic initializer.
Integrate with Apollo v1
On Apollo v1, DataDome plugs in via an InterceptorProvider. Use
DataDomeInterceptorProvider, which subclasses Apollo's DefaultInterceptorProvider and inserts the DataDome interceptor immediately after NetworkFetchInterceptor (and before ResponseCodeInterceptor) so it can inspect the raw HTTP response.
import Apollo
import DataDomeApollo
import CoreDataDome
private(set) lazy var apollo: ApolloClient = {
let store = ApolloStore(cache: InMemoryNormalizedCache())
// Reads ClientSideKey from the Info.plist `DataDome` dict (or use the programmatic initializer).
let dataDome = DataDome(configuration: dataDomeConfiguration)
guard let url = URL(string: "https://your-graphql-endpoint") else {
fatalError("Unable to create url")
}
let provider = DataDomeInterceptorProvider(store: store, dataDome: dataDome)
let transport = RequestChainNetworkTransport(
interceptorProvider: provider,
endpointURL: url
)
return ApolloClient(networkTransport: transport, store: store)
}()Run your operations exactly as you normally would.
apollo.fetch(query: MyQuery()) { result in
// Handle result
}Customizing the URLSession (v1)
The provider defaults its URLSessionClient. To supply your own, use the full initializer:
let provider = DataDomeInterceptorProvider(
client: URLSessionClient(),
store: store,
dataDome: dataDome
)Keep the
URLSessionClienton its default configuration so the DataDome cookie stored in the sharedHTTPCookieStorageis attached to retried requests
Integrate with Apollo v2
On Apollo v2, DataDome plugs in at the transport level instead of via an interceptor provider. Wrap your
session in DataDomeURLSession and pass it to Apollo v2's RequestChainNetworkTransport.
import Apollo
import DataDomeApollo
import CoreDataDome
private(set) lazy var apollo: ApolloClient = {
let store = ApolloStore(cache: InMemoryNormalizedCache())
let dataDome = DataDome(configuration: dataDomeConfiguration)
guard let url = URL(string: "https://your-graphql-endpoint") else {
fatalError("Unable to create url")
}
let transport = RequestChainNetworkTransport(
urlSession: DataDomeURLSession(dataDome: dataDome),
interceptorProvider: DefaultInterceptorProvider.shared, // your own / Apollo's default
store: store,
endpointURL: url
)
return ApolloClient(networkTransport: transport, store: store)
}()There is no DataDome interceptor to register, and the rest of your interceptor chain (including
your own interceptors) is untouched. Run your operations as usual:
let result = try await apollo.fetch(query: MyQuery())Customizing the wrapped session (v2)
DataDomeURLSession wraps URLSession(configuration: .default) by default. To wrap a different
ApolloURLSession, pass it via wrapping::
let session = DataDomeURLSession(dataDome: dataDome, wrapping: myApolloURLSession)Hard blocks (v2)
When DataDome serves a block page (the request cannot proceed), the transport throws
DataDomeError.blocked. Handle it where you execute operations:
do {
let result = try await apollo.fetch(query: MyQuery())
} catch DataDomeError.blocked {
// The request was blocked by DataDome.
}Troubleshooting
- Crash on launch /
configurationFromBundle()throws — theDataDomedictionary or its
ClientSideKeyis missing from yourInfo.plist. Add it (see Configure your DataDome client-side key) or use the programmatic initializer. No such module 'CoreDataDome'— add theCoreDataDomepackage product to your app target
(it is normally resolved transitively throughDataDomeApollo).Cannot find type 'DataDomeURLSession'(orDataDomeError) — you are building with the
ApolloV1trait; enable theApolloV2trait (and pinapollo-iosto 2.x).Cannot find type 'DataDomeInterceptorProvider'/DataDomeInterceptor— you are building with theApolloV2trait; these are the Apollo v1 types. UseDataDomeURLSessionfor v2.- Compile errors against the Apollo API — your enabled trait does not match the resolved
apollo-iosmajor. EnableApolloV1for Apollo 1.x orApolloV2for Apollo 2.x. Cannot find type 'CaptchaDelegate' / 'DataDomeURLSessionClient' / 'ApolloCompletion'— these were removed in 4.0.0. Challenge presentation is now automatic; seeMIGRATION.md.
Updated 16 days ago

