We often encounter this scenario: a corporate app is already working, but each new client requires their own parameters — server address, tenant ID, session timeouts. Without Managed App Configuration, you have to release a new build for each customer or hardcode tokens directly in the code, which is unsafe. Managed App Configuration cuts the integration time for a new client from 2 days to 2 hours — 10 times faster than releasing a separate build. According to Apple, over 70% of enterprise apps use this mechanism.
How Managed App Configuration Interacts with MDM?
The MDM server (Jamf, Intune, Workspace ONE) sends a plist dictionary to the app via the system key com.apple.configuration.managed. Your app reads it from UserDefaults. The data arrives automatically after a profile is installed on the device. No additional permissions are required — it works even in kiosk mode.
func loadManagedConfiguration() { guard let config = UserDefaults.standard.dictionary(forKey: "com.apple.configuration.managed") else { // Device is unmanaged or config not yet delivered applyDefaultConfiguration() return } let backendURL = config["BackendURL"] as? String ?? AppDefaults.backendURL let tenantID = config["TenantID"] as? String let sessionTimeout = config["SessionTimeoutMinutes"] as? Int ?? 30 let enableDebugLogs = config["EnableDebugLogs"] as? Bool ?? false AppConfig.shared.apply( backendURL: backendURL, tenantID: tenantID, sessionTimeout: sessionTimeout, debugLogs: enableDebugLogs ) } One pitfall: the configuration may not arrive immediately on first launch, but a few seconds after MDM check-in. The app should not block waiting for the config — we apply defaults, then update.
Why Reacting to Configuration Changes Matters?
MDM can update the config at any time — for example, change the backend URL during infrastructure migration. We need to react without restarting the app. We subscribe to UserDefaults.didChangeNotification and filter only the key com.apple.configuration.managed:
override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver( self, selector: #selector(managedConfigChanged), name: UserDefaults.didChangeNotification, object: nil ) } @objc private func managedConfigChanged() { guard let newConfig = UserDefaults.standard.dictionary(forKey: "com.apple.configuration.managed") else { return } let newBackendURL = newConfig["BackendURL"] as? String if newBackendURL != AppConfig.shared.backendURL { NetworkManager.shared.reconfigure(baseURL: newBackendURL) } } Filtering the key is mandatory — otherwise every minor UserDefaults change will reload the configuration. Apple Developer Documentation
Comparison of MDM Consoles for Managed App Configuration
| MDM solution | Input interface | Type support | Additional |
|---|---|---|---|
| Jamf Pro | XML profile (plist) in app | String, Integer, Boolean, Array | Can import JSON schema |
| Microsoft Intune | Key-value or XML | All basic types | App configuration policies for Managed Devices |
| VMware Workspace ONE | Plist file in console | String, Integer, Boolean | Flexible templates |
| MobileIron | XML dictionary | Limited set | Supports feedback key |
All four deliver data in the same format — com.apple.configuration.managed. The choice of console depends on the ecosystem, but our experience shows: Jamf is convenient for advanced scenarios, Intune for hybrid environments.
Comparison of Methods for Reacting to Configuration Changes
| Method | Reaction time to change | Implementation complexity |
|---|---|---|
| Polling every N seconds | ~N seconds | Low |
| Notification-based (didChangeNotification) | Instant | Medium |
| KVO on managed config key | Instant | High (requires NSObject) |
Notification-based approach gives instant reaction without unnecessary polling loops — the optimal choice for most projects.
Configuration Dictionary Structure
Recommended approach — a typed structure instead of manual casting from [AnyHashable: Any]. Configuration errors are caught at parsing stage:
struct ManagedConfig: Decodable { let backendURL: String let tenantID: String? let sessionTimeoutMinutes: Int let allowBiometricAuth: Bool let supportedLanguages: [String] let featureFlags: [String: Bool]? enum CodingKeys: String, CodingKey { case backendURL = "BackendURL" case tenantID = "TenantID" case sessionTimeoutMinutes = "SessionTimeoutMinutes" case allowBiometricAuth = "AllowBiometricAuth" case supportedLanguages = "SupportedLanguages" case featureFlags = "FeatureFlags" } } func decodeManagedConfig() -> ManagedConfig? { guard let dict = UserDefaults.standard.dictionary(forKey: "com.apple.configuration.managed"), let data = try? JSONSerialization.data(withJSONObject: dict), let config = try? JSONDecoder().decode(ManagedConfig.self, from: data) else { return nil } return config } Document the schema for the IT department — this speeds up deployment. As a bonus: MDM can read the app's state via the feedback key com.apple.feedback.managed, which simplifies diagnostics.
Testing Without an MDM Server
For development, we emulate configuration via UserDefaults.standard.set() in launch arguments or through a separate debug screen:
#if DEBUG func injectTestManagedConfig() { let testConfig: [String: Any] = [ "BackendURL": "https://staging-api.corp.example.com", "TenantID": "TEST-001", "SessionTimeoutMinutes": 5, "AllowBiometricAuth": true ] UserDefaults.standard.set(testConfig, forKey: "com.apple.configuration.managed") } #endif You can also use defaults write in Simulator — this imitates real delivery.
What's Included in Managed App Configuration Setup?
Our certified engineers with over 5 years of experience perform:
- Designing the configuration dictionary (JSON Schema)
- Implementing reading and change handling
- Integrating with existing business logic
- Test integration with your MDM (Jamf, Intune, Workspace ONE)
- Documentation for IT administrators
- Training for support team
We guarantee compatibility with App Store Review Guidelines (Section 4.2).
Typical Mistakes and How to Avoid Them
The most common implementation mistakes: blocking the UI while waiting for configuration — use defaults; not subscribing to changes — always handle UserDefaults.didChangeNotification; working with a raw dictionary instead of a typed structure; and lack of documentation for the IT department. Each of these issues is resolved at the design stage.
Phases and Timelines
- Analysis — 1 day
- Dictionary design — 1 day
- Development — 3–5 days
- Testing — 2–3 days
- MDM integration — 1 day
- Documentation and training — 1 day
Total: 1–2 weeks. The cost is calculated individually, depending on the app's complexity and number of parameters. We'll evaluate your project for free — just write to us. Get a consultation: we respond within 2 hours on business days. We'll help set up Managed App Configuration turnkey, with quality guarantee and post-deployment support. Contact us for a free consultation — we'll respond within 2 hours. Order Managed App Configuration setup today.







