Configuration profile reference
shft reads its admin policy from a macOS configuration profile delivered via MDM. The profile uses the preference domain com.shft.config.
How it works
When a configuration profile is present, shft enforces the admin's policy strictly. When no profile is present, shft runs in permissive default mode — all categories are available, all connection types are allowed, and users can add custom folders. This makes the app fully functional for trial and self-serve customers who are not using MDM.
Deploying the profile
- Create a custom configuration profile in your MDM with the preference domain
com.shft.config - Deploy it before or alongside the shft app — shft reads the profile on launch
- The profile can be updated at any time; shft re-reads values on each launch
- Target the profile at the user level so
UserDefaultscan read it
Key reference
| Key | Type | Default | Description |
|---|---|---|---|
shft.allowedDataCategories | Array of Strings | All categories | Which data categories users can migrate. Valid values: userFiles, applicationData, keychain, systemSettings, browserData. If omitted or empty, all categories are available. |
shft.allowUserOverride | Boolean | true | Whether users can add custom folders beyond the admin-defined categories. When false, users see only the categories listed in allowedDataCategories and cannot add anything else. |
shft.requireAdminApproval | Boolean | false | Reserved for future use. When implemented, migrations will require admin approval before starting. Scaffold this key now so profiles are forward-compatible. |
shft.maxTransferSizeMB | Integer | Unlimited | Maximum total transfer size in megabytes. If the user's selected data exceeds this limit, the "Begin Transfer" button is disabled and a warning is shown. Set to 0 or omit for no limit. |
shft.allowedConnectionTypes | Array of Strings | All types | Which connection types are available. Valid values: wifi, ethernet, thunderbolt. If omitted or empty, all types are allowed. |
shft.migrationWindowStart | String (HH:mm) | None | Start time of the allowed migration window in 24-hour format. If both start and end are set, users can only begin migrations during this window. Migrations already in progress are not interrupted. |
shft.migrationWindowEnd | String (HH:mm) | None | End time of the allowed migration window. Supports windows that span midnight (e.g., start 22:00, end 06:00). |
shft.logMigrationsToEndpoint | String (URL) | None | URL where shft POSTs a JSON migration log after each transfer completes. See Logging for the schema and setup instructions. |
shft.brandingName | String | None | Organisation name displayed on the welcome screen ("Managed by Acme Corp"). |
shft.brandingLogoURL | String (URL) | None | URL to an organisation logo image displayed on the welcome screen. Supports PNG, JPEG, and SVG. Recommended size: 120×80 pixels or smaller. The URL must be accessible from the Mac at launch time. |
shft.prefRestoreMode | String | staged | How migrated app preferences are applied on the destination. immediate writes them straight into ~/Library/... (legacy behaviour). staged (recommended) moves each app's prefs into ~/.shft-staged-prefs/<bundleKey>/ after the transfer and applies them automatically when the matching app appears in /Applications. manual stages the same way but waits for the user to apply each bundle from the shft UI. See Staged pref restore. |
shft.prefRestoreWindowDays | Integer | 7 | How many days a staged pref bundle is kept before being abandoned. If the matching app hasn't been installed by then, the bundle is surfaced to the user once and then discarded on the next launch sweep. |
shft.mdmAppManifestPath | String (path) | None | Path to a JSON file describing apps the MDM intends to install on this device. shft uses it to label staged bundles and to apply zero-touch hand-off without the user having to manage anything. Tilde expansion is supported. Schema documented in Staged pref restore. |
shft.mdmAppManifestJSON | String (JSON) | None | Inline alternative to shft.mdmAppManifestPath — the manifest as a JSON string embedded directly in the profile. Wins over the file path when both are set. |
shft.deploymentMode | String | interactive | interactive runs the setup wizard. managed hides the wizard and ManagedModeCoordinator drives discovery → pairing → migration without user interaction. See Managed mode. |
shft.deploymentRole | String | destination | Which side of the migration this device runs in managed mode. destination is the typical MDM-fleet rollout. source runs the source-side flow. |
shft.userPrincipal | String | None | Required when deploymentMode = managed. Typically the MDM variable $EMAIL, resolved per-device. Used to derive the destination's UserMapping without prompting. |
shft.migrationSource | String | auto | Discovery preference for managed mode. auto tries Bonjour first then Thunderbolt. bonjour / thunderbolt pin to one transport. |
shft.migrationCategories | Array of Strings | None | When set in managed mode, exactly these categories are selected (overriding the wizard's defaults). Same vocabulary as allowedDataCategories. |
shft.requireManagedSource | Boolean | false | When true, the destination won't accept pairing with a source that can't prove MDM enrollment. Enforced as a self-check on the destination today. |
shft.telemetryEndpoint | String (URL) | None | When set, ManagedModeCoordinator POSTs a JSON envelope to this URL at every lifecycle event (managed.start, managed.awaiting_peer, managed.pairing_complete, managed.complete, managed.failed, etc.). Fire-and-forget; failures log a warning but never block the migration. |
Example configuration profile
The following .mobileconfig contains all supported keys with example values:
<?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>PayloadContent</key>
<array>
<dict>
<!-- Preference domain for shft -->
<key>PayloadType</key>
<string>com.shft.config</string>
<key>PayloadIdentifier</key>
<string>com.shft.config.preferences</string>
<key>PayloadUUID</key>
<string>A1B2C3D4-E5F6-7890-ABCD-EF1234567890</string>
<key>PayloadVersion</key>
<integer>1</integer>
<!-- Only allow User Files, App Data, and Browser Data -->
<key>shft.allowedDataCategories</key>
<array>
<string>userFiles</string>
<string>applicationData</string>
<string>browserData</string>
</array>
<!-- Users cannot add custom folders -->
<key>shft.allowUserOverride</key>
<false/>
<!-- Reserved for future use -->
<key>shft.requireAdminApproval</key>
<false/>
<!-- Cap transfers at 50 GB -->
<key>shft.maxTransferSizeMB</key>
<integer>51200</integer>
<!-- Only allow Thunderbolt and Ethernet -->
<key>shft.allowedConnectionTypes</key>
<array>
<string>thunderbolt</string>
<string>ethernet</string>
</array>
<!-- Migrations only during business hours -->
<key>shft.migrationWindowStart</key>
<string>08:00</string>
<key>shft.migrationWindowEnd</key>
<string>18:00</string>
<!-- POST logs to internal endpoint -->
<key>shft.logMigrationsToEndpoint</key>
<string>https://logs.example.com/api/shft/migrations</string>
<!-- Organisation branding -->
<key>shft.brandingName</key>
<string>Acme Corp</string>
<key>shft.brandingLogoURL</key>
<string>https://cdn.example.com/acme-logo.png</string>
</dict>
</array>
<!-- Profile metadata -->
<key>PayloadDisplayName</key>
<string>shft Configuration</string>
<key>PayloadIdentifier</key>
<string>com.shft.config.profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>F1E2D3C4-B5A6-7890-1234-567890ABCDEF</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadScope</key>
<string>User</string>
</dict>
</plist>Permissive default mode
When no configuration profile is deployed, shft operates as if the following were set:
| Key | Default behaviour |
|---|---|
allowedDataCategories | All five categories available |
allowUserOverride | true — users can add custom folders |
requireAdminApproval | false |
maxTransferSizeMB | No limit |
allowedConnectionTypes | WiFi, Ethernet, and Thunderbolt all allowed |
migrationWindowStart/End | No time restriction |
logMigrationsToEndpoint | Logs saved locally only, not POSTed |
brandingName | Not shown |
brandingLogoURL | Default shft logo displayed |
This mode exists so shft is usable immediately during trials or for organisations that don't use MDM. As soon as a profile is detected with any shft key, the app switches to managed mode and enforces all configured restrictions.
Notes
- Profile updates: shft reads the profile on each launch. If you update the profile, users will see the new policy next time they open the app.
- Removing the profile: If you remove the profile, shft reverts to permissive default mode.
- Key validation: Invalid values (e.g., a non-URL string for
logMigrationsToEndpoint, an unrecognised category name) are silently ignored and the default is used instead. - Forward compatibility: Unrecognised
shft.*keys in the profile are ignored without error, so a profile written for a newer shft version still works on older clients. - Generating UUIDs: Use
uuidgenin Terminal to generate unique PayloadUUID values for your profile.