Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
143 lines (107 sloc) 6.28 KB

AMP Shadow Doc API


AMP can be used in a shadow-doc mode where a single web page can open many AMP documents. This is especially useful for shell-style PWA documents.

Using shadow doc API

The special runtime should be used in place of v0.js. It can be declared in the shell page as following:

<script async src=""></script>

<!-- Wait for API to initialize -->
(window.AMP = window.AMP || []).push(function(AMP) {
  // AMP APIs can be used now via "AMP" object.


Fetching and attaching shadow docs

There are currently two ways how one can attach a shadow doc: using a Document object, loaded, for instance, via XHR. Or using an experimental streaming API. Once streaming API graduates from experimental, the non-streaming API will be deprecated.

Using the fully loaded Document object:

fetchDocumentViaXhr(url).then(fetchedDoc => {
  const shadowDoc = AMP.attachShadowDoc(hostElement, fetchedDoc, url, options);

Using the streaming API:

const shadowDoc = AMP.attachShadowDocAsStream(hostElement, url, options);
fetch(url).then(response => {
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  function readChunk() {
    return => {
      const text = decoder.decode(
          chunk.value || new Uint8Array(),
          {stream: !chunk.done});
      if (text) {
      if (chunk.done) {
      } else {
        return readChunk();
  return readChunk();

Notice, that XHR and Fetch API are only some of the sources of documents. Other sources could include local storage and other. AMP APIs assume a basic HTML streaming. The details of raw HTTP streaming are outside of AMP APIs, but examples below provide additional information and prototypes.

Visibility state (visibilityState)

The options argument is optional and can provide configuration parameters for AMP document. The most relevant of these options is visibilityState. By default it takes the value of "visible", but can be configured to "prerender" mode instead. Prerender mode can be used for minimal prerendering of the element. In this mode most of features are disabled, including analytics and ads. The mode can be later changed to "visibile" via shadowDoc.setVisibilityState() function.

Shadow-doc API

Both AMP.attachShadowDoc and AMP.attachShadowDocAsStream return a ShadowDoc object that provides numerous ways for interracting with attached AMP documents. This object exposes the following methods and properties:

  • shadowDoc.writer - the writer that can be used to stream the AMP document. Only available for attachShadowDocAsStream.
  • shadowDoc.url - the URL used in the attachShadowDoc or attachShadowDocAsStream.
  • shadowDoc.title - the title of the AMP document.
  • shadowDoc.canonicalUrl - the canonical URL of the AMP document.
  • shadowDoc.ampdoc - the instance of the AMP document.
  • shadowDoc.ampdoc.whenReady() - returns a promise when the AMP document has been fully rendered.
  • shadowDoc.setVisibilityState() - changes the visibility state of the AMP document.
  • shadowDoc.postMessage() and shadowDoc.onMessage() - can be used to message with the AMP document.
  • shadowDoc.close() - closes the AMP document and frees the resources.

Shadow DOM API and polyfills

AMP Shadow Docs rely heavily on the Shadow DOM API. This is a powerful and elegant API, part of the Web Components family. It allows for natural isolation between major parts of the page and, as such, is an ideal tool for PWAs and AMP Shadow Docs.

Shadow DOM is currently only implemented in Chrome and newer Safari. AMP Shadow Docs API internally polyfills the necessary parts of Shadow DOM.

However, not all advanced Shadow DOM features are polyfilled by AMP Shadow Docs. In particular, shadow slots are not polyfilled. If you'd like to use slots and similar advanced features, please use one of the Shadow DOM polyfill, such as WebComponents.js. If you do, we recommend the following code structure (using Web Components polyfills as an example):


<script async src=""></script>
<script async src=""></script>


const ampReadyPromise = new Promise(resolve => {
  (window.AMP = window.AMP || []).push(resolve);
const sdReadyPromise = new Promise(resolve => {
  if (Element.prototype.attachShadow) {
    // Native available.
  } else {
    // Otherwise, wait for polyfill to be installed.
    window.addEventListener('WebComponentsReady', resolve);
Promise.all([ampReadyPromise, sdReadyPromise]).then(() => {
  return AMP.attachShadowDocAsStream(...);

The working example can be found in pwa.js sample.

We tested with WebComponents.js polyfill, but this should work transparently with any other polyfill. Let us know if you run into difficulties with other polyfills.

Examples and references

See pwa.js for examples of uses of boths APIs.

See Combine AMP with PWA and Embed & use AMP as a data source guides.