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.

Download the template profile
Ready-to-customize .mobileconfig with every supported key. Edit the values, regenerate the PayloadUUIDs with uuidgen, then deploy through your MDM.
shft-config.mobileconfig
JSON Schema for the profile keys
For Jamf admins: paste into Custom Schema for a guided profile form. For everyone else: use it as a JSON Schema validator or IDE autocomplete source.
com.shft.config.schema.json
Download

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 UserDefaults can read it

Key reference

KeyTypeDefaultDescription
shft.allowedDataCategoriesArray of StringsAll categoriesWhich data categories users can migrate. Valid values: userFiles, applicationData, keychain, systemSettings, browserData. If omitted or empty, all categories are available.
shft.allowUserOverrideBooleantrueWhether 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.requireAdminApprovalBooleanfalseReserved for future use. When implemented, migrations will require admin approval before starting. Scaffold this key now so profiles are forward-compatible.
shft.maxTransferSizeMBIntegerUnlimitedMaximum 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.allowedConnectionTypesArray of StringsAll typesWhich connection types are available. Valid values: wifi, ethernet, thunderbolt. If omitted or empty, all types are allowed.
shft.migrationWindowStartString (HH:mm)NoneStart 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.migrationWindowEndString (HH:mm)NoneEnd time of the allowed migration window. Supports windows that span midnight (e.g., start 22:00, end 06:00).
shft.logMigrationsToEndpointString (URL)NoneURL where shft POSTs a JSON migration log after each transfer completes. See Logging for the schema and setup instructions.
shft.brandingNameStringNoneOrganisation name displayed on the welcome screen ("Managed by Acme Corp").
shft.brandingLogoURLString (URL)NoneURL 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.prefRestoreModeStringstagedHow 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.prefRestoreWindowDaysInteger7How 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.mdmAppManifestPathString (path)NonePath 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.mdmAppManifestJSONString (JSON)NoneInline 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.deploymentModeStringinteractiveinteractive runs the setup wizard. managed hides the wizard and ManagedModeCoordinator drives discovery → pairing → migration without user interaction. See Managed mode.
shft.deploymentRoleStringdestinationWhich side of the migration this device runs in managed mode. destination is the typical MDM-fleet rollout. source runs the source-side flow.
shft.userPrincipalStringNoneRequired when deploymentMode = managed. Typically the MDM variable $EMAIL, resolved per-device. Used to derive the destination's UserMapping without prompting.
shft.migrationSourceStringautoDiscovery preference for managed mode. auto tries Bonjour first then Thunderbolt. bonjour / thunderbolt pin to one transport.
shft.migrationCategoriesArray of StringsNoneWhen set in managed mode, exactly these categories are selected (overriding the wizard's defaults). Same vocabulary as allowedDataCategories.
shft.requireManagedSourceBooleanfalseWhen 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.telemetryEndpointString (URL)NoneWhen 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:

KeyDefault behaviour
allowedDataCategoriesAll five categories available
allowUserOverridetrue — users can add custom folders
requireAdminApprovalfalse
maxTransferSizeMBNo limit
allowedConnectionTypesWiFi, Ethernet, and Thunderbolt all allowed
migrationWindowStart/EndNo time restriction
logMigrationsToEndpointLogs saved locally only, not POSTed
brandingNameNot shown
brandingLogoURLDefault 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 uuidgen in Terminal to generate unique PayloadUUID values for your profile.