← BACK TO HOME

Building DDM Declarations, Part 1: The Data Schema Shift (XML to JSON)

Explains the shift from XML configuration profiles to DDM JSON declarations, mapping legacy payload concepts to Type, Identifier, ServerToken, and Payload.

The first major hurdle in Declarative Device Management is not the API, the MDM vendor interface, or the automation language used to build payloads. The first hurdle is the schema.

For years, Apple administrators have worked with XML property lists, usually delivered as .mobileconfig configuration profiles. Windows administrators coming from Group Policy, Configuration Manager, or Intune can think of these profiles as structured policy documents: a profile contains one or more payloads, each payload has a type, and each payload carries settings for a specific management domain.

Declarative Device Management changes that shape. It does not merely replace XML syntax with JSON syntax. It changes the unit of management from a profile full of payload dictionaries to a set of individually identified declarations. Each declaration has a stable identity, a declaration type, an opaque server-controlled version marker, and a payload whose structure is defined by that declaration type.

This article focuses only on that data schema shift: from XML configuration profile payloads to JSON DDM declarations.

The Legacy Profile Shape

A traditional Apple configuration profile is a property list. In its common XML form, it looks roughly like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>PayloadType</key>
  <string>Configuration</string>

  <key>PayloadIdentifier</key>
  <string>com.example.profile.security</string>

  <key>PayloadUUID</key>
  <string>11111111-2222-3333-4444-555555555555</string>

  <key>PayloadDisplayName</key>
  <string>Security Baseline</string>

  <key>PayloadVersion</key>
  <integer>1</integer>

  <key>PayloadContent</key>
  <array>
    <dict>
      <key>PayloadType</key>
      <string>com.apple.mobiledevice.passwordpolicy</string>

      <key>PayloadIdentifier</key>
      <string>com.example.profile.security.passcode</string>

      <key>PayloadUUID</key>
      <string>aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee</string>

      <key>PayloadVersion</key>
      <integer>1</integer>

      <key>minLength</key>
      <integer>8</integer>

      <key>forcePIN</key>
      <true/>
    </dict>
  </array>
</dict>
</plist>

That structure has two important layers.

The outer profile identifies the profile as a whole. The inner payload dictionaries define the actual settings. Each payload has its own PayloadType, PayloadIdentifier, PayloadUUID, and payload-specific keys.

For Windows administrators, the closest mental model is a GPO or Configuration Manager baseline that bundles multiple setting areas together. The profile is the container. The payloads are the setting blocks. The payload type tells the client how to interpret the keys inside that block.

The DDM Declaration Shape

A DDM declaration is JSON, but the more important difference is its structure. A declaration is not an XML profile wrapper. It is an independently identified object.

The common declaration shape is:

{
  "Type": "com.apple.configuration.passcode.settings",
  "Identifier": "com.example.config.passcode-baseline",
  "ServerToken": "2026-05-28T16:00:00Z",
  "Payload": {
    "RequirePasscode": true,
    "MinimumLength": 8,
    "RequireComplexPasscode": true
  }
}

The four core fields are:

Type tells the client what kind of declaration this is.

Identifier gives the declaration a stable name.

ServerToken gives the server a way to mark the current version of the declaration.

Payload contains the declaration-specific data.

This is the first correction to make when moving from profiles to declarations: DDM declarations do not use DeclarationIdentifier, PayloadType, or PayloadContent as their top-level schema. Those names are tempting because they feel familiar from configuration profiles, but they are not the DDM declaration shape.

Type

The Type field identifies the exact declaration type.

In a configuration profile, PayloadType usually identifies a profile payload domain such as a Wi-Fi payload, restrictions payload, certificate payload, or passcode payload. In DDM, Type identifies the declaration itself.

Examples include:

"com.apple.activation.simple"
"com.apple.configuration.passcode.settings"
"com.apple.configuration.management.status-subscriptions"
"com.apple.asset.credential.scep"

The declaration type determines how the client interprets the Payload object. A passcode settings declaration has a different payload schema than a status subscription declaration. An activation declaration has a different payload schema than both of them.

That is a major shift from the legacy profile model. In the profile model, the outer profile is generic and the payload dictionaries carry the setting types. In DDM, each declaration is already typed at the top level.

Identifier

The Identifier field is the stable identity of the declaration.

A good identifier should be unique, predictable, and durable. It should not change every time the payload changes. The identifier is what other declarations reference.

For example:

"Identifier": "com.franca.tech.config.passcode-baseline.v1"

The identifier is similar in spirit to a GPO GUID, a Configuration Manager configuration item unique ID, or an Intune policy ID. It is the name the management system and the client use to distinguish this declaration from every other declaration.

Do not treat the identifier as a version number. If the same policy changes from an eight-character minimum passcode to a twelve-character minimum passcode, the identifier should generally remain stable. The content changed, not the identity of the policy.

There is also a practical size limit: Apple’s declaration schema says the Identifier string should not exceed 64 octets. That matters when designing naming conventions. A long reverse-DNS prefix, environment name, platform name, policy category, and version label can easily become longer than expected. Keep identifiers descriptive, but keep them short enough to fit the schema.

ServerToken

The ServerToken field is an opaque server-controlled version marker.

For example:

"ServerToken": "2026-05-28T16:00:00Z"

or:

"ServerToken": "sha256-f4c2b7e9..."

The client does not need to understand what the token means. It only needs to know whether the token changed.

This is directly comparable to versioning concepts that Windows administrators already understand. Group Policy has version information. Configuration Manager baselines and applications have revision history. Intune policies have object versions and service-side state. The point is the same: the client needs a cheap way to know whether it is looking at the same instruction or a changed instruction.

In DDM, the ServerToken is the server’s way of saying, “this is the current version of this declaration.” When the declaration content changes, the server should provide a different token.

The ServerToken also has a practical size limit: Apple’s declaration schema says the value should not exceed 64 octets. A plain ISO 8601 timestamp is comfortably within that limit. A SHA-256 hex string is exactly 64 characters, which is a useful upper-bound pattern. Avoid long prefixed values such as production-passcode-baseline-sha256-... because the prefix plus hash can exceed the limit.

Payload

The Payload field contains the declaration-specific data.

For a passcode configuration, the payload contains passcode settings:

{
  "Type": "com.apple.configuration.passcode.settings",
  "Identifier": "com.franca.tech.config.passcode-baseline.v1",
  "ServerToken": "2026-05-28T16:00:00Z",
  "Payload": {
    "RequirePasscode": true,
    "MinimumLength": 8,
    "RequireComplexPasscode": true,
    "MaximumFailedAttempts": 10,
    "MaximumGracePeriodInMinutes": 5
  }
}

For an activation declaration, the payload identifies which configurations should become active:

{
  "Type": "com.apple.activation.simple",
  "Identifier": "com.franca.tech.activation.security-baseline.v1",
  "ServerToken": "2026-05-28T16:05:00Z",
  "Payload": {
    "StandardConfigurations": [
      "com.franca.tech.config.passcode-baseline.v1"
    ]
  }
}

The payload shape is not universal. It is controlled by the declaration type. That is the rule to follow when authoring declarations programmatically.

Activations Are Not Configuration Payloads

Activations deserve special attention because they are easy to misunderstand.

A configuration declaration defines a setting. An activation declaration determines whether one or more configurations should be active.

The relationship looks like this:

{
  "Type": "com.apple.activation.simple",
  "Identifier": "com.franca.tech.activation.security-baseline.v1",
  "ServerToken": "2026-05-28T16:05:00Z",
  "Payload": {
    "StandardConfigurations": [
      "com.franca.tech.config.passcode-baseline.v1"
    ],
    "Predicate": "(@status(device.operating-system.family) == 'macOS')"
  }
}

The StandardConfigurations array contains identifiers of configuration declarations. It does not embed the configuration objects themselves. That distinction matters when building these objects with code.

The Predicate is part of the activation declaration’s payload. If it is present, the activation applies only when the predicate evaluates to true. If it is absent, the activation is not gated by that additional predicate condition.

A legacy profile often contains all of its payloads inside one PayloadContent array. A DDM activation references configurations by identifier. The activation is the linkage layer.

Mapping Legacy Profile Concepts to DDM Concepts

The shift is easier to work with when the old and new structures are mapped directly.

A .mobileconfig XML profile maps to one or more DDM JSON declarations.

A profile payload dictionary maps most closely to a configuration declaration.

A legacy PayloadType maps conceptually to the DDM declaration Type, but not one-to-one in every case. DDM has its own declaration type names.

A legacy PayloadIdentifier or PayloadUUID maps conceptually to the DDM Identifier.

A legacy payload’s settings map to the DDM Payload.

A legacy profile revision or management-system version maps conceptually to the DDM ServerToken.

A legacy profile containing multiple payload dictionaries maps to multiple declarations, usually tied together by one or more activation declarations.

This is where the Windows analogy becomes useful. A GPO is often treated as a large linked container. A DDM declaration is closer to a discrete desired-state object. The activation layer determines when that desired-state object applies.

OS and Schema Compatibility Matter

DDM is not a single static capability that applies identically to every Apple operating system version.

Apple’s declaration schemas include supported operating systems, allowed enrollments, allowed scopes, and key-level availability. A declaration type may be available on one platform but not another. A declaration type may be available on macOS, while a specific key inside that declaration requires a later macOS version. Some declarations apply only in system scope, user scope, or specific enrollment types.

For example, the passcode settings declaration is available on macOS, but individual keys have their own support details and behavior notes. That means a production authoring workflow should validate both the declaration type and the specific payload keys against Apple’s current schema for the target OS versions and enrollment types.

Do not assume that a key is valid just because it appears in an example. Treat examples as schema patterns, then verify them against the Apple schema and the MDM vendor workflow that will deliver them.

Why JSON Matters

JSON is not automatically better than XML. Bad JSON is just as fragile as bad XML. The improvement comes from the combination of DDM’s declaration model and JSON’s natural fit for modern API workflows.

JSON maps cleanly to native objects in PowerShell, JavaScript, Python, Go, and most other automation languages. That means declarations can be built as structured objects instead of assembled as text. Arrays remain arrays. Booleans remain booleans. Integers remain integers. Nested objects remain nested objects.

That matters because DDM payloads are not simple strings. They are structured instructions. Treating them as structured data is the only sane way to build them at scale.

The Practical Rule

When building DDM declarations, start with this rule:

A declaration is an object with Type, Identifier, ServerToken, and Payload.

Everything else flows from that.

The Type tells the client what the declaration is. The Identifier gives it a stable name. The ServerToken marks the current version. The Payload carries the declaration-specific content.

Once that structure is clear, the next step is building it safely. Part 2 of this series moves from schema comprehension to object construction, showing how PowerShell hashtables, arrays, and nested objects can generate valid DDM JSON without fragile string manipulation.