Migration Guide
Migration Guide: 6.x to 7.x
This guide helps you upgrade an existing integration from the Embrace Apple 6.x SDK to 7.x. 7.0 is a major version with a number of breaking changes. Most are mechanical renames, but two are conceptual and worth reading in full: the single entry point and the redefined session model.
Key Changes Summary
- Single entry point.
EmbraceIOis now the only public client. The lower-levelEmbraceclient andEmbrace.Optionsare no longer public. - One-step startup.
Embrace.setup(options:).start()/EmbraceIO.setup(options:)+start()are replaced by a singletry EmbraceIO.start(options:). - OpenTelemetry configuration changed. Custom exporters move from
Options.exporttoOptions.otel(EmbraceIO.OTelOptions), which also controls access to the OpenTelemetry providers and whether they are registered globally. - Embrace-owned public types. Spans, logs, and related types are now Embrace types (
EmbraceSpan,EmbraceType, etc.) instead of raw OpenTelemetry types. You no longer need toimport OpenTelemetryApiin most cases. - Redefined sessions. A "session" is now a user session made of one or more session parts.
- Non-throwing APIs. Most instrumentation calls (
log,setProperty, personas, spans) no longerthrow. - Removed options and APIs, including
appGroupId,startNewSession()/endCurrentSession(),userName/userEmail, and theEmbraceOTelInternalmodule. - No Objective-C API. The 7.x public API is Swift-only. See Objective-C.
Migrate with Claude Code
If you use Claude Code, a skill handles the whole migration: it inventories every 6.x call site in your codebase, rewrites the startup call and the renamed APIs, flags the removals that need a decision from you, and builds the project to verify the result.
Download both files:
- SKILL.md — the migration procedure
- API_MAPPING.md — the verified 6.x to 7.0 API reference
Place both in a single folder under your Claude Code skills directory:
~/.claude/skills/embrace-apple-sdk-6to7-migration/
├── SKILL.md
└── API_MAPPING.md
Then, from your app repository, ask Claude Code to "Migrate this app from Embrace 6 to 7." The rest of this guide covers the same changes if you'd rather migrate by hand.
Single Entry Point: EmbraceIO
In 6.x there were two ways to use the SDK: the EmbraceIO convenience layer and the lower-level Embrace client (Embrace.client?...). In 7.x, EmbraceIO is the single public interface, and it configures and starts the SDK in one call. Embrace.Options is no longer public.
// 6.x
try Embrace
.setup(options: Embrace.Options(appId: "YOUR_APP_ID"))
.start()
// ...or via the convenience layer:
try EmbraceIO.setup(options: EmbraceIO.Options.withAppId("YOUR_APP_ID"))
try EmbraceIO.shared.start()
// 7.x
try EmbraceIO.start(
options: EmbraceIO.Options.withAppId("YOUR_APP_ID")
)
Everywhere your code used Embrace.client?.someMethod(...), use EmbraceIO.shared.someMethod(...) instead. Metadata methods that used to live under Embrace.client?.metadata are now directly on EmbraceIO.shared.
Options Changes
EmbraceIO.Options is created through factory methods (withAppId, withLocalConfiguration) rather than a public initializer.
| 6.x | 7.x |
|---|---|
appGroupId: | Removed |
crashReporter: EmbraceCrashReporter() | crashReporter: .embrace (enum .embrace/.crashlytics/.none) |
endpoints: Embrace.Endpoints(...) | endpoints: EmbraceEndpoints(...) |
platform: (Platform) | platform: (EmbracePlatform) |
export: OpenTelemetryExport(...) | otel: EmbraceIO.OTelOptions(...) |
processors: [...] | otel: EmbraceIO.OTelOptions(spanProcessors:/logProcessors:) |
runtimeConfiguration: | Use the EmbraceIO.Options.withLocalConfiguration(...) factory |
OpenTelemetry Export
Custom exporters are the biggest structural change to Options. In 6.x they were passed via export; in 7.x they go through the otel option, which accepts arrays of exporters and processors:
// 6.x
Embrace.Options(
appId: "YOUR_APP_ID",
export: OpenTelemetryExport(
spanExporter: myCustomSpanExporter,
logExporter: myCustomLogRecordExporter
)
)
// 7.x
EmbraceIO.Options.withAppId(
"YOUR_APP_ID",
otel: EmbraceIO.OTelOptions(
spanExporters: [myCustomSpanExporter],
logExporters: [myCustomLogRecordExporter]
)
)
Your SpanExporter / LogRecordExporter implementations themselves do not change — they still come from OpenTelemetrySdk. Only the way you register them changes. See OpenTelemetry Export.
For an integration with no Embrace App ID (export only), use withLocalConfiguration, where otel is required:
EmbraceIO.Options.withLocalConfiguration(
otel: EmbraceIO.OTelOptions(
spanExporters: [OtlpHttpTraceExporter(/* ... */)]
)
)
OpenTelemetry Providers
Embrace.client?.tracer(instrumentationName:) becomes EmbraceIO.shared.tracer(instrumentationName:instrumentationVersion:), and there is now a matching logger(instrumentationScopeName:instrumentationVersion:). Both return nil unless you started the SDK with an EmbraceIO.OTelOptions, so pass one — an empty EmbraceIO.OTelOptions() is enough if you have no exporters to register.
// 6.x
let tracer = Embrace.client?.tracer(instrumentationName: "com.mycompany.checkout")
// 7.x
try EmbraceIO.start(
options: EmbraceIO.Options.withAppId("YOUR_APP_ID", otel: EmbraceIO.OTelOptions())
)
let tracer = EmbraceIO.shared.tracer(instrumentationName: "com.mycompany.checkout")
One behavioral change to watch for: 6.x always registered the Embrace providers as the process-wide OpenTelemetry providers, so third-party instrumentation resolving OpenTelemetry.instance emitted through Embrace automatically. In 7.x that registration is opt-in — pass registersGlobalProviders: true in your OTelOptions to keep the 6.x behavior. See OpenTelemetry Providers.
Embrace-Owned Types
7.x no longer exposes raw OpenTelemetry types in its public API. Where you previously imported and used OpenTelemetry types, use the Embrace equivalents (all re-exported by EmbraceIO):
| 6.x (OpenTelemetry) | 7.x (Embrace) |
|---|---|
Span / SpanBuilder | EmbraceSpan |
SpanType | EmbraceType |
SpanEvent | EmbraceSpanEvent |
SpanStatus | EmbraceSpanStatus |
SpanErrorCode | EmbraceSpanErrorCode |
LogSeverity | EmbraceLogSeverity |
[String: String] attrs | EmbraceAttributes |
A practical consequence: to hold a span in a property you no longer need import OpenTelemetryApi.
// 6.x
import OpenTelemetryApi
var activitySpan: Span?
// 7.x
var activitySpan: EmbraceSpan?
Traces and Spans
The span builder (buildSpan(...).startSpan()) is replaced by a single createSpan(...) call that returns an EmbraceSpan?.
// 6.x
let span = EmbraceIO.shared.buildSpan(name: "add-cart-item", type: .performance)
.startSpan()
let child = EmbraceIO.shared.buildSpan(name: "ui-update")
.setParent(span)
.startSpan()
span?.addEvent(name: "quantity-changed")
span?.end(errorCode: .failure)
// 7.x
let span = EmbraceIO.shared.createSpan(name: "add-cart-item", type: .performance)
let child = EmbraceIO.shared.createSpan(name: "ui-update", parentSpan: span)
span?.addEvent(name: "quantity-changed", attributes: [:])
span?.end(errorCode: .failure)
Notes:
setParent(_:)becomes theparentSpan:argument ofcreateSpan.EmbraceTypevalues are unchanged in spelling:.performance,.ux,.system(and semantic types like.networkRequest,.view).span.end(errorCode:)still works. The error code enum was renamedSpanErrorCode→EmbraceSpanErrorCode(the cases.failure,.userAbandon,.unknownare unchanged). You can also passautoTerminationCode:tocreateSpanto have a span automatically end with an error code when the session part ends.- The
Embrace.recordSpan(name:) { ... }closure helper is removed; create the span and end it yourself. addEvent(name:attributes:)requires theattributesargument (pass[:]if none).
Logs
log(...) no longer throws, and its severity/attribute types changed.
// 6.x
try? EmbraceIO.shared.log("message", severity: .warning, attributes: ["k": "v"])
// 7.x
EmbraceIO.shared.log("message", severity: .warn, attributes: ["k": "v"])
- The severity type is
EmbraceLogSeverity. Note the spelling.warn(not.warning)..criticalis also available. - Attributes are
EmbraceAttributes([String: EmbraceAttributeValue]), which now acceptsString,Int,Double, andBoolvalues — not only strings.
Breadcrumbs and Session Events
// 6.x
EmbraceIO.shared.add(event: .breadcrumb("checkout started"))
EmbraceIO.shared.add(event: SpanEvent(name: "milestone", attributes: [...]))
// 7.x
EmbraceIO.shared.addBreadcrumb("checkout started")
EmbraceIO.shared.addSessionEvent(name: "milestone", attributes: [...])
Metadata and Personas
Metadata methods now live directly on EmbraceIO.shared, no longer throw, and personas are plain String values instead of PersonaTag.
// 6.x
try? Embrace.client?.metadata.add(persona: PersonaTag("authenticated"), lifespan: .session)
try? EmbraceIO.shared.setProperty(key: "tier", value: "gold", lifespan: .session)
// 7.x
EmbraceIO.shared.addPersona("authenticated", lifespan: .userSession)
EmbraceIO.shared.setProperty(key: "tier", value: "gold", lifespan: .userSession)
userNameanduserEmailare removed. OnlyuserIdentifierremains for user metadata (EmbraceIO.shared.userIdentifier = "id"; set tonilto clear).clearUserProperties()is removed. Clear state withuserIdentifier = nil,removeAllProperties(lifespans:), and/orremoveAllPersonas(lifespans:).MetadataLifespan.sessionis renamed toMetadataLifespan.userSession. The other two cases (.processand.permanent) are unchanged. The rename follows the redefined session model: the metadata is scoped to the whole user session, so it is now included in every session part of that user session (and survives process death for as long as the user session does) instead of being tied to a single foreground or background interval.
// 6.x — applies to one foreground/background session
EmbraceIO.shared.addPersona("authenticated", lifespan: .session)
// 7.x — applies to every part of the current user session
EmbraceIO.shared.addPersona("authenticated", lifespan: .userSession)
Redefined Session Model
This is the most important conceptual change. In 6.x each foreground or background interval was a separate "session". In 7.x those intervals are session parts, and one or more parts are grouped into a user session bounded by a configurable maximum duration (default 12 hours) and inactivity timeout (default 30 minutes). See Sessions for the full model.
API impact:
| 6.x | 7.x |
|---|---|
currentSessionId / currentSessionId() | currentUserSessionId (property) |
startNewSession() | Removed |
endCurrentSession() | Removed |
endCurrentSession() + startNewSession() | endUserSession() |
// 6.x
Embrace.client?.endCurrentSession()
Embrace.client?.startNewSession()
// 7.x
EmbraceIO.shared.endUserSession()
endUserSession() ends the current user session and immediately starts a new one (with the same foreground/background state). It is rate-limited to once every 5 seconds.
New attribute keys are emitted on session part spans and logs, including emb.user_session_id, emb.session_part_id, and session.id. The 6.x emb.session_number is superseded by emb.session_part_number, and the new emb.user_session_part_index gives the 1-indexed position of a part within its user session. Update any dashboards, saved queries, alerts, or downstream OpenTelemetry pipelines keyed on emb.session_number — nothing fails at build time, they simply stop matching.
Crash Reporting
Crash reporting is now built into EmbraceIO and selected through the crashReporter enum (you no longer need any additional imports or extra products like you did in 6.x — just set the crashReporter option).
// 6.x
EmbraceIO.Options.withAppId("YOUR_APP_ID", crashReporter: EmbraceCrashReporter())
// ...and, for Crashlytics, adding the EmbraceCrashlyticsSupport product plus:
// crashReporter: CrashlyticsReporter()
// 7.x
EmbraceIO.Options.withAppId("YOUR_APP_ID", crashReporter: .embrace)
// For Crashlytics (no extra product needed — it's bundled):
EmbraceIO.Options.withAppId("YOUR_APP_ID", crashReporter: .crashlytics)
// To install no crash reporter:
EmbraceIO.Options.withAppId("YOUR_APP_ID", crashReporter: .none)
Custom crash info is added with the non-throwing EmbraceIO.shared.appendCrashInfo(key:value:).
CrashReporter is no longer a public protocol, so custom crash reporter conformances don't compile against 7.x. Choose one of the bundled reporters instead.
Remove the 6.x crash products from every target
The EmbraceCrash and EmbraceCrashlyticsSupport package products no longer exist. Removing the imports is not enough: a target can reference a package product even when no source file imports it, and a leftover reference fails the build with Missing package product 'EmbraceCrash'.
In Xcode, check the Frameworks, Libraries, and Embedded Content list of every target, including watch apps and extensions, and remove both products. Each target that uses Embrace should end up with only EmbraceIO (plus EmbraceMacros if you use the macros). To find stray references, search the project file:
grep -nE "EmbraceCrash|EmbraceCrashlyticsSupport" YourApp.xcodeproj/project.pbxproj
Capture Services
The capture-services builder was renamed and its registration methods changed.
// 6.x
let services = CaptureServiceBuilder()
.add(.urlSession(options: URLSessionCaptureService.Options()))
.addDefaults()
.build()
// 7.x
let services = CaptureServicesOptionsBuilder()
.addDefaults()
.addUrlSessionCaptureService(withOptions: URLSessionCaptureService.Options())
.build()
let options = EmbraceIO.Options.withAppId("YOUR_APP_ID", captureServices: services)
Per-service registration now uses dedicated methods: addUrlSessionCaptureService(withOptions:), addViewCaptureService(withOptions:), addTapCaptureService(withOptions:), addWebViewCaptureService(withOptions:), addPushNotificationCaptureService(withOptions:), addLowMemoryWarningCaptureService(), addLowPowerModeCaptureService(), and addHangCaptureService(). See Capture Services.
Modules and Installation
- Update your dependency to 7.x. Swift Package Manager is the recommended integration method — point your
EmbraceIOpackage dependency at a 7.x release. - CocoaPods is also supported:
pod 'EmbraceIO', '~> 7.0.0'. - See Installation.
- The
EmbraceOTelInternalmodule is removed; the internal OTel bridge is nowEmbraceOTelBridge. Users still justimport EmbraceIO. - There is no separate
EmbraceCrash/EmbraceCrashlyticsSupportproduct to add — crash reporting (including Crashlytics support) is bundled and selected via thecrashReporteroption. Remove both products from every target, as described in Crash Reporting. - If you use the
EmbraceMacrosproduct, Xcode asks you to trust the SDK's macro plugin again after the upgrade. See Installation. - Minimum requirements moved to Swift 5.9 / Xcode 26.0 or later, and the minimum deployment targets are raised to iOS/iPadOS 15+, tvOS 15+ and watchOS 9+ (macOS stays at 13+). If your app targets an older OS version, raise its deployment target before upgrading. Since April 28, 2026, Apple requires apps uploaded to App Store Connect to be built with Xcode 26 and the OS 26 SDKs. See Requirements.
Objective-C
The 7.x public API has no @objc surface, so Objective-C call sites can't be migrated. Move those call sites to Swift, or contact Embrace support if your app needs to call Embrace from Objective-C.
Removed APIs Reference
| 6.x API | 7.x replacement |
|---|---|
Embrace.setup(...).start() | EmbraceIO.start(options:) |
EmbraceIO.setup(...) + start() | EmbraceIO.start(options:) |
Embrace.client?.X | EmbraceIO.shared.X |
buildSpan(...).startSpan() | createSpan(...) |
SpanErrorCode | EmbraceSpanErrorCode (span.end(errorCode:) unchanged) |
recordSpan(name:) { } | createSpan(...) + span.end() |
add(event:) | addBreadcrumb(_:) / addSessionEvent(...) |
OpenTelemetryExport | EmbraceIO.OTelOptions |
Embrace.client?.tracer(instrumentationName:) | EmbraceIO.shared.tracer(instrumentationName:) (requires otel:) |
startNewSession() / endCurrentSession() | endUserSession() |
currentSessionId | currentUserSessionId |
PersonaTag("x") | "x" (plain String) |
MetadataLifespan.session | MetadataLifespan.userSession |
userName / userEmail | Removed (use userIdentifier) |
clearUserProperties() | removeAllProperties(lifespans:) / userIdentifier = nil |
appGroupId | Removed |
resetUploadCache() | Removed |
EmbraceOTelInternal | EmbraceOTelBridge (internal) |
Verify Your Integration
- Build and run your app, and confirm the SDK started by checking
EmbraceIO.shared.state == .started. - Send your app to the background so the current session part is uploaded, then confirm the session appears in your Embrace dashboard.
- Exercise the parts of your app that use custom instrumentation, and confirm the spans, logs, and metadata you had on 6.x still arrive.
Next Steps
After migrating to 7.x, we recommend:
- Review Sessions to understand the new user-session/parts model
- Confirm your custom exporters are registered through
EmbraceIO.OTelOptions - Re-check any code that stored spans or referenced OpenTelemetry types directly
- If you rely on third-party OpenTelemetry instrumentation, decide whether to set
registersGlobalProviders: true
Need Help?
The 7.x FAQ covers the questions we hear most often about 7.0, including why the SDK moved to Embrace-owned types and how to keep exporting to your own OpenTelemetry backend.
If you encounter issues during migration, please reach out on the community slack or email support@embrace.io.