This document is a quickstart guide for using Shared Storage and Private Aggregation. You'll need an understanding of both APIs because Shared Storage stores the values and Private Aggregation creates the aggregatable reports.
Target Audience: Ad techs and measurement providers.
Try the demo
Try out the live demo. Follow the steps in the demo instructions to enable the Privacy Sandbox APIs. Opening Chrome DevTools helps you visualize the results of different use cases. Use cases available in the demo:
- Private Aggregation
- Unique Reach Measurement
- Demographics measurement
- K+ frequency measurement
- General Usage
- Measure hover-over event inside fenced frames
- Top-level navigation
- Controlling where third parties can write
How to view Shared Storage
To view what is stored in Shared Storage, use Chrome DevTools. Stored data can
be found in Application -> Shared Storage
.
View reports for Private Aggregation
To view the aggregatable reports sent, navigate to
chrome://private-aggregation-internals
. When debug mode is enabled, a report
is sent immediately (without a delay) to
[[YOUR_ORIGIN]]/.well-known/private-aggregation/debug/report-shared-storage
along with the time-delayed report to be sent to
[[YOUR_ORIGIN]]/.well-known/private-aggregation/report-shared-storage
.
To enable debugging, follow the instructions in the debugging section.
Shared Storage API
To prevent cross-site tracking, browsers have started partitioning all forms of storage, including local storage, cookies, and so forth. But there are use cases where unpartitioned storage is required. The Shared Storage API provides unlimited write access across different top-level sites with privacy-preserving read access.
Shared Storage is restricted to the context origin (the caller of
sharedStorage
).
Shared Storage has a capacity limit per origin, with each entry limited to a maximum number of characters. If the limit is reached, no further inputs are stored. The data storage limits are outlined in the Shared Storage explainer.
Invoking Shared Storage
Ad techs can write to Shared Storage using JavaScript or response headers. Reading from Shared Storage only occurs within an isolated JavaScript environment called a worklet.
Using JavaScript Ad techs can perform specific Shared Storage functions such as setting, appending, and deleting values outside of a JavaScript worklet. However, functions such as reading Shared Storage and performing Private Aggregation have to be completed through a JavaScript worklet. Methods that can be used outside of a JavaScript worklet can be found in Proposed API Surface - Outside the worklet.
Methods that are used in the worklet during an operation can be found in Proposed API Surface - In the worklet.
Using response headers
Similar to JavaScript, only specific functions such as setting, appending, and deleting values in Shared Storage can be done using response headers. To work with Shared Storage in a response header,
Shared-Storage-Writable: ?1
has to be included in the request header.To initiate a request from the client, run the following code, depending on your chosen method:
Using
fetch()
fetch("https://a.example/path/for/updates", {sharedStorageWritable: true});
Using an
iframe
orimg
tag<iframe src="https://a.example/path/for/updates" sharedstoragewritable></iframe>
Using an IDL attribute with an
iframe
orimg
taglet iframe = document.getElementById("my-iframe"); iframe.sharedStorageWritable = true; iframe.src = "https://a.example/path/for/updates";
Further information can be found in Shared Storage: Response Headers.
Writing to Shared Storage
To write to Shared Storage, call sharedStorage.set()
from inside or outside a
JavaScript worklet. If called from outside the worklet, the data is written to
the origin of the browsing context that the call was made from. If called from
inside the worklet, the data is written to the origin of the browsing context
that loaded the worklet. The keys that are set have an expiration date of 30
days from last update.
The ignoreIfPresent
field is optional. If present and set to true
, the key
is not updated if it already exists. Key expiration is renewed to 30 days from
the set()
call even if the key is not updated.
If Shared Storage is accessed multiple times in the same page load with the same
key, the value for the key is overwritten. It's a good idea to use
sharedStorage.append()
if the key needs to maintain the previous value.
Using JavaScript
Outside the worklet:
window.sharedStorage.set('myKey', 'myValue1', { ignoreIfPresent: true }); // Shared Storage: {'myKey': 'myValue1'} window.sharedStorage.set('myKey', 'myValue2', { ignoreIfPresent: true }); // Shared Storage: {'myKey': 'myValue1'} window.sharedStorage.set('myKey', 'myValue2', { ignoreIfPresent: false }); // Shared Storage: {'myKey': 'myValue2'}
Similarly, inside the worklet:
sharedStorage.set('myKey', 'myValue1', { ignoreIfPresent: true });
Using response headers
You can also write to Shared Storage using response headers. To do so, use
Shared-Storage-Write
in the response header along with the following commands:Shared-Storage-Write : set;key="myKey";value="myValue";ignore_if_present
Shared-Storage-Write : set;key="myKey";value="myValue";ignore_if_present=?0
Multiple items can be comma-separated and can combine
set
,append
,delete
, andclear
.Shared-Storage-Write : set;key="hello";value="world";ignore_if_present, set;key="good";value="bye"
Appending a value
You can append a value to an existing key using the append method. If the key
does not exist, calling append()
creates the key and sets the value. This can
be accomplished using JavaScript or response headers.
Using JavaScript
To update values of existing keys, use
sharedStorage.append()
from either inside or outside the worklet.window.sharedStorage.append('myKey', 'myValue1'); // Shared Storage: {'myKey': 'myValue1'} window.sharedStorage.append('myKey', 'myValue2'); // Shared Storage: {'myKey': 'myValue1myValue2'} window.sharedStorage.append('anotherKey', 'hello'); // Shared Storage: {'myKey': 'myValue1myValue2', 'anotherKey': 'hello'}
To append inside the worklet:
sharedStorage.append('myKey', 'myValue1');
Using response headers
Similar to setting a value in Shared Storage, you can use the
Shared-Storage-Write
in the response header to pass in the key-value pair.Shared-Storage-Write : append;key="myKey";value="myValue2"
Reading from Shared Storage
You can read from Shared Storage only from within a worklet.
await sharedStorage.get('mykey');
The origin of the browsing context that the worklet module was loaded from determines whose Shared Storage is read.
Deleting from Shared Storage
You can perform deletes from Shared Storage using JavaScript from either inside
or outside the worklet or by using response headers with delete()
. To delete
all keys at once, use clear()
from either.
Using JavaScript
To delete from Shared Storage from outside the worklet:
window.sharedStorage.delete('myKey');
To delete from Shared Storage from inside the worklet:
sharedStorage.delete('myKey');
To delete all keys at once from outside the worklet:
window.sharedStorage.clear();
To delete all keys at once from inside the worklet:
sharedStorage.clear();
Using response headers
To delete values using response headers, you can also use
Shared-Storage-Write
in the response header to pass the key to be deleted.delete;key="myKey"
To delete all keys using response headers:
clear;
Context switching
Shared Storage data is written to the origin (for example, https://example.adtech.com) of the browsing context that the call originated from.
When you load the third-party code using a <script>
tag, the code is executed
in the browsing context of the embedder. Therefore, when the third-party code
calls sharedStorage.set()
, the data is written to the embedder's Shared
Storage. When you load the third-party code within an iframe, the code receives
a new browsing context, and its origin is the origin of the iframe. Therefore,
the sharedStorage.set()
call made from the iframe stores the data into the
Shared Storage of the iframe origin.
First-party context
If a first-party page has embedded third-party JavaScript code that calls
sharedStorage.set()
or sharedStorage.delete()
, the key-value pair is stored
in the first-party context.
Third-party context
The key-value pair can be stored in the ad tech or third-party context by
creating an iframe and calling set()
or delete()
in the JavaScript code from
within the iframe.
Private Aggregation API
To measure aggregatable data stored in Shared Storage, you can use the Private Aggregation API.
To create a report, call contributeToHistogram()
inside a worklet with a
bucket and value. The bucket is represented by an unsigned 128-bit integer which
must be passed into the function as a BigInt
. The value is a positive integer.
To protect privacy, the report's payload, which contains the bucket and value, is encrypted in transit, and it can only be decrypted and aggregated using the Aggregation Service.
The browser will also limit the contributions a site can make to an output query. Specifically, the contribution budget limits the total of all reports from a single site for a given browser in a given time window across all buckets. If the current budget is exceeded, a report will not be generated.
privateAggregation.contributeToHistogram({
bucket: BigInt(myBucket),
value: parseInt(myBucketValue)
});
Executing Shared Storage and Private Aggregation
You must create a worklet to access data from shared storage. To do this, call
createWorklet()
with the URL of the worklet. By default, when using shared
storage with createWorklet()
, the data partition origin will be the invoking
browsing context's
origin, not the origin of the worklet script itself.
To change the default behaviour, set the dataOrigin
property when calling
createWorklet
.
dataOrigin: "context-origin"
: (Default) Data is stored in the shared storage of the invoking browsing context's origin.dataOrigin: "script-origin"
: Data is stored in the shared storage of the worklet script's origin. Note that an opt-in is required to enable this mode.
sharedStorage.createWorklet(scriptUrl, {dataOrigin: "script-origin"});
To opt-in, when using "script-origin"
, the script endpoint will have to
respond with the header Shared-Storage-Cross-Origin-Worklet-Allowed
. Note that
CORS should be enabled for
cross-origin requests.
Shared-Storage-Cross-Origin-Worklet-Allowed : ?1
Using cross-origin iframe
An iframe is needed to invoke the shared storage worklet.
In the ad's iframe, load the worklet module by calling addModule()
. To run the
method that is registered in the sharedStorageWorklet.js
worklet file, in the
same ad iframe JavaScript, call sharedStorage.run()
.
const sharedStorageWorklet = await window.sharedStorage.createWorklet(
'https://any-origin.example/modules/sharedStorageWorklet.js'
);
await sharedStorageWorklet.run('shared-storage-report', {
data: { campaignId: '1234' },
});
In the worklet script, you will need to create a class with an async run
method and register
it to run in the ad's iframe. Inside
sharedStorageWorklet.js
:
class SharedStorageReportOperation {
async run(data) {
// Other code goes here.
bucket = getBucket(...);
value = getValue(...);
privateAggregation.contributeToHistogram({
bucket,
value
});
}
}
register('shared-storage-report', SharedStorageReportOperation);
Using cross-origin request
Shared Storage and Private Aggregation allows creation of cross-origin worklets without the need for cross-origin iframes.
The first-party page can also invoke a createWorklet()
call to the
cross-origin javascript endpoint. You will need to set the data partition origin
of the worklet to be that of the script-origin when creating the worklet.
async function crossOriginCall() {
const privateAggregationWorklet = await sharedStorage.createWorklet(
'https://cross-origin.example/js/worklet.js',
{ dataOrigin: 'script-origin' }
);
await privateAggregationWorklet.run('pa-worklet');
}
crossOriginCall();
The cross-origin javascript endpoint will have to respond with the headers
Shared-Storage-Cross-Origin-Worklet-Allowed
and note that CORS is enabled for
the request.
Shared-Storage-Cross-Origin-Worklet-Allowed : ?1
Worklets created using the createWorklet()
will have selectURL
and run()
.
addModule()
is not available for this.
class CrossOriginWorklet {
async run(data){
// Other code goes here.
bucket = getBucket(...);
value = getValue(...);
privateAggregation.contributeToHistogram({
bucket,
value
});
}
}
Debugging
To enable debugging, call the enableDebugMode()
JavaScript method in the same
context where Shared Storage and Private Aggregation is used. This will be
applied for future reports in the same context.
privateAggregation.enableDebugMode();
To associate the reports with the contexts that triggered them, you can set a
64-bit unsigned integer debug key which is passed to the JavaScript call. The
debugKey
is a BigInt
.
privateAggregation.enableDebugMode({debugKey: 1234});
Debugging Shared Storage
Shared Storage returns a generic error message:
Promise is rejected without and explicit error message
You can debug Shared Storage by wrapping the calls with try-catch blocks.
try {
privateAggregation.contributeToHistogram({bucket, value});
} catch (e){
console.log(e);
}
Debugging Private Aggregation
Reports are sent to /.well-known/private-aggregation/report-shared-storage
and
/.well-known/private-aggregation/debug/report-shared-storage
. Debug reports
receive a payload similar to the following JSON. This payload defines the api
field as "shared-storage".
{
"aggregation_coordinator_origin": "https://publickeyservice.msmt.gcp.privacysandboxservices.com",
"aggregation_service_payloads": [ {
"debug_cleartext_payload": "omRkYXRhlKJldmFsdWVEAAAAgGZidWNrZXRQAAAAAAAAAAAAAAAAB1vNFaJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAKJldmFsdWVEAAAAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAAAGlvcGVyYXRpb25paGlzdG9ncmFt",
"key_id": "1569ab37-da44-4a26-80d9-5ed6524ab2a7",
"payload": "/9nHrWn1MnJWRxFvanbubciWE9mPyIij6uYLi5k351eQCd3/TZpe2knaatUNcniq4a4e61tmKebv50OmMRZFnnCfcAwIdIgLHu1a3en97PojqWJBfO52RiVMIcP7KQTLzMxq2LhvPSdV4zjXo1Teu/JuIK3LIyis3vUMpS+tUAX0QV+I6X5SVmZFiNW9aMb8DwLOtqrBy5JJ/EkOIY0G+1Fi1/3R7UtKsqM1o71A/OzdmlNkwO7EV/VUNinGvWnd19FvDHe/kqkNdTHKbhAnMmbZzHW9bsEQS81leElCla6BTdbdbeeFU/jbTj0AOaoNOIe5r7FU5NG6nW4ULXTCbLLjTQ1mtl3id3IP41Zin1JvABCDC/HUSgLFz8EUqkmbMIOlMfNYA79aURq6FqE0GO0HtICYf0GPNdVv7p4jY3FNn6+JS4l5F3t+3lP9ceo4IpCE+31jzMtYJ+19xFh6C5ufteBR/iknZFcc1w3caQBhgRl5jt8DbaOzYcW4690H8Ul4Oh2wRO+6/njifk+pExLay/O5swLi2lUUph5OUEaaztwwzh2mnhwIBxMkPnfsGihiF+5KDEajVfMZ3NLsIDoZO+l4RTZrkqE+jVkAqaZGBiCIx42Edp/JV0DXfrryypCdQBZr8iEbSzCM9hKsMfLN7S/VkPe5rDwOZbhKCn5XXgfGz5tSx/KbZgsQf4OCEhwAyNPHAh3MHU7xmkQ3pKg4EIUC/WOtKAlVDOtDMmPPoQY1eAwJhw9SxZaYF1kHjUkTm3EnGhgXgOwCRWqeboNenSFaCyp6DbFNI3+ImONMi2oswrrZO+54Tyhca5mnLIiInI+C3SlP4Sv1jFECIUdf/mifJRM5hMj6OChzHf4sEifjqtD4A30c4OzGexWarie2xakdQej9Go4Lm0GZEDBfcAdWLT9HwmpeI2u4HDAblXDvLN8jYFDOOtzOl90oU7AwdhkumUCFLRadXAccXW9SvLfDswRkXMffMJLFqkRKVE1GPonFFtlzaRqp7IgE8L6AOtz6NDcxAjHnEuzDPPMcWMl1AFH0gq7h"
} ],
"debug_key": "1234",
"shared_info": "{\"api\":\"shared-storage\",\"debug_mode\":\"enabled\",\"report_id\":\"80d93c0a-a94e-4ab7-aeb5-a4ecd4bfc598\",\"reporting_origin\":\"https://privacy-sandbox-demos-dsp.dev\",\"scheduled_report_time\":\"1717784740\",\"version\":\"0.1\"}"
}
Debug cleartext payload
The debug_cleartext_payload
is
Base64
CBOR-encoded. You can view the bucket and
value using the decoder or use
the JavaScript code found in the Shared Storage
decoder.
Next steps
The following pages explain important aspects of the Shared Storage and Private Aggregation APIs.
- Introduction to Shared Storage (Developer Chrome)
- Shared Storage Use Cases (Developer Chrome)
- Introduction to Private Aggregation (Developer Chrome)
- Shared Storage Explainer (GitHub)
- Private Aggregation Explainer (GitHub)
- Shared Storage and Private Aggregation Demo
Once you are acquainted with the APIs, you can start collecting the reports, which are sent as a POST request to the following endpoints as JSON in the request body.
- Debug Reports -
context-origin/.well-known/private-aggregation/debug/report-shared-storage
- Reports -
context-origin/.well-known/private-aggregation/report-shared-storage
Once reports are collected, you can test using the local testing tool or set up the Trusted Execution Environment for Aggregation Service to get the aggregated reports.
Share your feedback
You can share your feedback on the APIs and documentation on GitHub.