The profile sync controller helps developers synchronize data across multiple clients and devices in a privacy-preserving way. All data saved in the user storage database is encrypted client-side to preserve privacy. The user storage provides a modular design, giving developers the flexibility to construct and manage their storage spaces in a way that best suits their needs
yarn add @metamask/profile-sync-controller
or
npm install @metamask/profile-sync-controller
You can import the controllers via the main npm path.
import { ... } from '@metamask/profile-sync-controller'This package also uses subpath exports, which help minimize the amount of code you wish to import. It also helps keep specific modules isolated, and can be used to import specific code (e.g. mocks). You can see all the exports in the package.json, but here are a few.
Importing specific controllers/modules:
// Import the AuthenticationController and access its types/utilities
import { ... } from '@metamask/profile-sync-controller/auth'
// Import the UserStorageController and access its types/utilities
import { ... } from '@metamask/profile-sync-controller/user-storage'
// Import the profile-sync SDK and access its types/utilities
import { ... } from '@metamask/profile-sync-controller/sdk'Importing mock creation functions:
// Import and use mock creation functions (designed to mirror the actual types).
// Useful for testing or Storybook development.
import { ... } from '@metamask/profile-sync-controller/auth/mocks'
import { ... } from '@metamask/profile-sync-controller/user-storage/mocks'AuthenticationController exposes UI-independent primitives for passkey and
email OTP enrollment and verification:
refreshEnrolledCredentials()refreshes the in-memory credential list.beginCredentialEnrollment()andcompleteCredentialEnrollment()surround a client-owned passkey ceremony or email-code screen. Once the profile has a credential that proves AAL2, the server requires an AAL2 token to begin enrolling another one:beginCredentialEnrollment()sends the verification token only while a session younger thanENROLLMENT_MAX_SESSION_AGE_MS(2 minutes) is live, and the server otherwise rejects it withaal2_required, so clients should verify an existing credential and retry. The controller never inspects the token's assurance level; the server decides. A setup flow that proved a factor itself can passmaxSessionAgeMs(for example, the time since the flow started) so chained enrollments reuse that proof.completeCredentialEnrollment()opens a verification session with the assertion the server returns for the new credential (replacing any earlier one), so no separate verification is needed right after enrolling.beginCredentialVerification()andcompleteCredentialVerification()verify an enrolled credential and return a verification token.getVerificationToken()reuses a live verification session when it satisfies the caller's freshness requirement;clearVerificationSession()clears it. The session lasts as long as the server says the token does (expires_in, measured from when it was obtained) and ends on lock, sign-out, reset, or a rejected base session. It is a low-level read: features should go through the client MFA kit (verifyOrEnroll), which reuses a matching session without showing any screen. Read it directly only from code that cannot show UI, and treatnullas "let the UI layer ask".
Clients must retain the challenge flowId, perform the platform ceremony, and
send the resulting proof to the matching completion method. OTP codes,
passkey results, and verification tokens are never persisted in controller state.
This package is part of a monorepo. Instructions for contributing can be found in the monorepo README.