Skip to main content

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. EmbraceIO is now the only public client. The lower-level Embrace client and Embrace.Options are no longer public.
  • One-step startup. Embrace.setup(options:).start() / EmbraceIO.setup(options:) + start() are replaced by a single try EmbraceIO.start(options:).
  • OpenTelemetry configuration changed. Custom exporters move from Options.export to Options.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 to import OpenTelemetryApi in 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 longer throw.
  • Removed options and APIs, including appGroupId, startNewSession()/endCurrentSession(), userName/userEmail, and the EmbraceOTelInternal module.
  • 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:

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.x7.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 / SpanBuilderEmbraceSpan
SpanTypeEmbraceType
SpanEventEmbraceSpanEvent
SpanStatusEmbraceSpanStatus
SpanErrorCodeEmbraceSpanErrorCode
LogSeverityEmbraceLogSeverity
[String: String] attrsEmbraceAttributes

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 the parentSpan: argument of createSpan.
  • EmbraceType values are unchanged in spelling: .performance, .ux, .system (and semantic types like .networkRequest, .view).
  • span.end(errorCode:) still works. The error code enum was renamed SpanErrorCode → EmbraceSpanErrorCode (the cases .failure, .userAbandon, .unknown are unchanged). You can also pass autoTerminationCode: to createSpan to 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 the attributes argument (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). .critical is also available.
  • Attributes are EmbraceAttributes ([String: EmbraceAttributeValue]), which now accepts String, Int, Double, and Bool values — not only strings.
// 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)
  • userName and userEmail are removed. Only userIdentifier remains for user metadata (EmbraceIO.shared.userIdentifier = "id"; set to nil to clear).
  • clearUserProperties() is removed. Clear state with userIdentifier = nil, removeAllProperties(lifespans:), and/or removeAllPersonas(lifespans:).
  • MetadataLifespan.session is renamed to MetadataLifespan.userSession. The other two cases (.process and .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.x7.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 EmbraceIO package dependency at a 7.x release.
  • CocoaPods is also supported: pod 'EmbraceIO', '~> 7.0.0'.
  • See Installation.
  • The EmbraceOTelInternal module is removed; the internal OTel bridge is now EmbraceOTelBridge. Users still just import EmbraceIO.
  • There is no separate EmbraceCrash / EmbraceCrashlyticsSupport product to add — crash reporting (including Crashlytics support) is bundled and selected via the crashReporter option. Remove both products from every target, as described in Crash Reporting.
  • If you use the EmbraceMacros product, 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 API7.x replacement
Embrace.setup(...).start()EmbraceIO.start(options:)
EmbraceIO.setup(...) + start()EmbraceIO.start(options:)
Embrace.client?.XEmbraceIO.shared.X
buildSpan(...).startSpan()createSpan(...)
SpanErrorCodeEmbraceSpanErrorCode (span.end(errorCode:) unchanged)
recordSpan(name:) { }createSpan(...) + span.end()
add(event:)addBreadcrumb(_:) / addSessionEvent(...)
OpenTelemetryExportEmbraceIO.OTelOptions
Embrace.client?.tracer(instrumentationName:)EmbraceIO.shared.tracer(instrumentationName:) (requires otel:)
startNewSession() / endCurrentSession()endUserSession()
currentSessionIdcurrentUserSessionId
PersonaTag("x")"x" (plain String)
MetadataLifespan.sessionMetadataLifespan.userSession
userName / userEmailRemoved (use userIdentifier)
clearUserProperties()removeAllProperties(lifespans:) / userIdentifier = nil
appGroupIdRemoved
resetUploadCache()Removed
EmbraceOTelInternalEmbraceOTelBridge (internal)

Verify Your Integration​

  1. Build and run your app, and confirm the SDK started by checking EmbraceIO.shared.state == .started.
  2. Send your app to the background so the current session part is uploaded, then confirm the session appears in your Embrace dashboard.
  3. 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:

  1. Review Sessions to understand the new user-session/parts model
  2. Confirm your custom exporters are registered through EmbraceIO.OTelOptions
  3. Re-check any code that stored spans or referenced OpenTelemetry types directly
  4. 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.