Published: October 23, 2026
In Chrome 155 and later, Digital Credentials API issuance is enabled by default, so you no longer need an origin trial token or a flag to use it. The Digital Credentials API is a transport layer that lets websites connect to wallet apps installed on the user's mobile device. With issuance, issuers can add new credentials (such as digital IDs) directly into a user's wallet app.
Background
The digital identity ecosystem has three parties: issuers, verifiers (also called relying parties), and holders (wallet apps). Users often add a digital ID to their wallet app through push provisioning, where the user starts issuance from the issuer's website or app. For example, a student on their university website (issuer) can save their digital student ID to their preferred wallet app (holder).
For this model to be successful, the issuer needs a secure and reliable way to invoke the wallet app to issue the ID. Before this API, each issuer had to build its own way to open the wallet apps it partnered with, usually with custom URI schemes. Issuers had to maintain a separate mechanism for each wallet app, and users had no consistent way to choose a wallet app.
How digital credential issuance works
To streamline this process, Chrome supports issuance with the
W3C Digital Credentials API. This
adds to the presentation capabilities
(sharing requested, selective data from a digital credential with a verifier),
which shipped in Chrome 141. When an issuer website calls
navigator.credentials.create() with a digital member that contains an
issuance request, Chrome passes the request to the operating system (on Android,
Credential Manager). The operating system then shows its own interface where the
user chooses a wallet app. The API works with industry-standard protocols such
as OpenID for Verifiable Credential Issuance (OpenID4VCI).
Issuance supports two flows: a same-device flow on Android, and a cross-device
flow that starts on desktop and finishes on an Android device.
Same-device flow on Android
On Android, Chrome passes the issuance request to Android Credential Manager, which shows a list of compatible wallet apps on the device. The user selects a wallet and consents to adding the credential. The wallet app then gets the credential from the issuer using the issuance protocol, such as OpenID4VCI.
Cross-device flow on desktop
On desktop, Chrome displays a QR code. The user scans the code with their Android device and completes the flow on that device, as described in the previous section. The devices use Bluetooth to confirm that they are close to each other, so Chrome asks the user to turn on Bluetooth if it's off. Chrome uses the same cross-device mechanism for Digital Credentials API presentation.
Implement issuance using the API
To start the issuance flow, call navigator.credentials.create() and pass your
issuance requests inside a digital member. Make this call from a click handler
or another user gesture handler, because the call requires transient user
activation. The following example uses an OpenID4VCI credential offer with the
pre-authorized code flow:
// Call this function from a user gesture handler, for example, a button's
// click handler. The pre-authorized code is a short-lived, single-use code
// that your server generates for the signed-in user.
async function issueDigitalCredential(preAuthorizedCode) {
// Check for Digital Credentials API and protocol support.
if (!window.DigitalCredential ||
!DigitalCredential.userAgentAllowsProtocol('openid4vci-v1')) {
// Provide an alternative issuance method.
showAlternativeIssuanceFlow();
return;
}
// Construct a credential offer according to the OpenID4VCI
// specification. This example uses the format that the
// demo CMWallet supports.
const credentialOffer = {
credential_issuer: 'https://issuer.example',
credential_configuration_ids: ['org.iso.18013.5.1.mDL'],
grants: {
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
'pre-authorized_code': preAuthorizedCode
}
},
// Metadata that the wallet uses to request and display the credential.
credential_issuer_metadata: {
credential_issuer: 'https://issuer.example',
credential_endpoint: 'https://issuer.example/openid4vci/credential',
nonce_endpoint: 'https://issuer.example/openid4vci/nonce',
credential_configurations_supported: {
'org.iso.18013.5.1.mDL': {
format: 'mso_mdoc',
doctype: 'org.iso.18013.5.1.mDL',
// Bind the credential to a key that the wallet generates.
cryptographic_binding_methods_supported: ['cose_key'],
proof_types_supported: {
jwt: { proof_signing_alg_values_supported: ['ES256'] }
},
credential_metadata: {
display: [{ name: 'Mobile driving license', locale: 'en-US' }],
// The claims that this credential contains. For a full list of
// standard mDL claims, see the ISO/IEC 18013-5 specification.
claims: [{
path: ['org.iso.18013.5.1', 'family_name'],
display: [{ name: 'Family name', locale: 'en-US' }]
}
// ... Add any other claims for your credential here.
]
}
}
}
},
// Metadata of the authorization server that issues access tokens.
authorization_server_metadata: {
issuer: 'https://issuer.example',
token_endpoint: 'https://issuer.example/openid4vci/token'
}
};
try {
const credential = await navigator.credentials.create({
digital: {
requests: [{
// The protocol identifier for OpenID4VCI 1.0.
protocol: 'openid4vci-v1',
// Pass the offer as an object, not as a JSON string.
data: credentialOffer
}]
}
});
// The wallet handled the request. credential.data is an object that
// contains the wallet's protocol-specific response. It is not the issued
// credential, and it does not necessarily mean that issuance is complete.
// Your server knows when the wallet retrieves the credential from your
// credential endpoint.
// Implement your custom logic here as needed.
showIssuanceStatus(credential);
} catch (error) {
switch (error.name) {
case 'NotAllowedError':
// The user canceled the flow, no wallet can handle the request, or
// the call does not have transient user activation.
break;
case 'NotSupportedError':
// The browser does not support Digital Credentials API issuance.
// Fall back to your custom alternative flow logic.
showAlternativeIssuanceFlow();
break;
case 'TypeError':
// The request is malformed, for example, data is not an object.
console.error('Invalid issuance request:', error);
break;
default:
// For example, AbortError or NetworkError.
console.error('Credential issuance failed:', error);
}
}
}
const addButton = document.querySelector('#add-to-wallet');
addButton.addEventListener('click', () => {
// Your server adds a pre-authorized code for the signed-in user
// to the button's data-pre-authorized-code attribute.
issueDigitalCredential(addButton.dataset.preAuthorizedCode);
});
In this example, showAlternativeIssuanceFlow() and showIssuanceStatus()
stand in for your own app logic.
To call create() from a cross-origin iframe, the embedding page must allow the
digital-credentials-create permissions policy feature. Otherwise, the call
rejects with a NotAllowedError:
<iframe src="https://issuer.example" allow="digital-credentials-create">
</iframe>
Changes since the origin trial
The API methods and parameters haven't changed since the origin trial. However, the following changes shipped between Chrome 143 and Chrome 155, and some of them might require you to update your code. The following table summarizes the changes, and the sections after it explain each one.
| Area | Origin trial (Chrome 143) | Chrome 155 |
|---|---|---|
| Availability | Origin trial token or the chrome://flags/#web-identity-digital-credentials-creation flag |
Enabled by default |
| User activation | Required, but not consumed | Required and consumed by each create() call (Chrome 146) |
| Cancellation errors | NetworkError |
NotAllowedError (Chrome 148) |
| Protocol identifiers | Chrome passes any identifier to the operating system | Unrecognized identifiers trigger a deprecation warning (Chrome 151) |
| Opaque origins | Allowed | Blocked with NotAllowedError (Chrome 151) |
| Automated testing | No built-in support | Virtual wallet in the Chrome DevTools Protocol (Chrome 152) |
User activation is consumed
Starting from Chrome 146, a create() call with a digital member consumes the
transient user activation,
as the specification requires. During the origin trial, Chrome checked for user
activation but didn't consume it.
As a result, each create() call needs its own user gesture, for example, a
button click. Without user activation, the promise rejects with a
NotAllowedError. After the call, window.open() and other APIs that require
user activation can't use the same gesture.
Cancellation rejects with NotAllowedError
Starting in Chrome 148, the promise that create() returns rejects with a
NotAllowedError when the user cancels the flow, for example, by dismissing the
wallet selector on Android or closing the QR code dialog on desktop. The promise
also rejects with a NotAllowedError when no wallet app can handle the request.
During the origin trial, these cases were rejected with a NetworkError.
If your code checks for a NetworkError to detect cancellation, update it to
check for a NotAllowedError instead.
Unrecognized protocol identifiers trigger a warning
The specification defines a single issuance protocol identifier,
openid4vci-v1, for OpenID4VCI 1.0. Chrome 155 recognizes openid4vci-v1, and
also openid4vci. Because openid4vci isn't part of the specification, we
recommend that you use openid4vci-v1.
Chrome passes requests with any protocol identifier to the platform. However,
starting from Chrome 151, DevTools shows a deprecation warning when a page calls
create() with an unrecognized protocol identifier. Chrome intends to block
requests with unrecognized protocol identifiers. For status, see the
More specific protocol filtering in Digital Credential API entry.
To test this behavior, enable
chrome://flags/#enable-experimental-web-platform-features. With this flag
enabled, create() ignores requests with unrecognized protocol identifiers and
rejects with a TypeError if no requests remain. Also,
DigitalCredential.userAgentAllowsProtocol() returns true only for recognized
protocols. Without the flag, it only checks that the identifier is well formed.
So, don't rely on userAgentAllowsProtocol() alone for feature detection.
Handle NotSupportedError as shown in the example.
Opaque origins are blocked
Starting from Chrome 151, create() rejects with a NotAllowedError when a
document with an
opaque origin
calls it, such as a sandboxed iframe without the allow-same-origin token.
During the origin trial, Chrome did not block these calls.
Test with a virtual wallet
Starting from Chrome 152, you can test your issuance flow without an Android
device or a wallet app. The experimental
DigitalCredentials.setVirtualWalletBehavior command in the
Chrome DevTools Protocol (CDP)
implements the virtual wallet that the specification defines for automated
testing. The virtual wallet can respond with the data that you provide, decline
requests as if the user canceled the flow, or leave requests pending to test
timeouts and aborts.
The following Puppeteer test sets the virtual wallet to
respond to openid4vci-v1 requests, then clicks the button that starts
issuance:
const client = await page.createCDPSession();
await client.send('DigitalCredentials.setVirtualWalletBehavior', {
action: 'respond',
protocol: 'openid4vci-v1',
response: {}
});
// Clicking the button provides the user activation that create() requires.
await page.click('#add-to-wallet');
Industry adoption
We see Digital Credentials API issuance adoption. For example, the German financial institution Sparkasse uses Digital Credentials API to let account holders save verified age credentials to Google Wallet. Account holders can then use these credentials to verify their age online and in person.
Try it out
Before you test the Digital Credentials API issuance, make sure that you have the following:
- Update to Chrome 155 or later.
- Install a supported wallet app on your Android device, for example, the demo CMWallet.
- Bluetooth turned on for both devices (cross-device flow only).
To test the cross-device flow:
- On your computer, open the demo issuer site.
- Click the Add button.
- Scan the QR code with your Android device and follow the onscreen instructions.
To test the same-device flow, open the demo issuer site in Chrome on your Android device.
Developer resources
To start integrating Digital Credentials API issuance into your website, use the following resources:
- Read the developer guide by the W3C Web Identity & Credentials Adoption Community Group.
- Read the W3C Digital Credentials specification.
- Read the OpenID for Verifiable Credential Issuance 1.0 specification.
- Explore the source code of the demo CMWallet, an open source demo wallet app for Android.
- Learn how Android wallet apps handle issuance requests from Credential Manager.
- Try the demo issuer site with the demo CMWallet installed on your Android device.
- Explore the Digital Credentials request constructor demo to see what a request payload looks like.
Share your feedback
To report a bug or request a feature, file an issue in the Chromium bug tracker.