Other Publishing Formats

You can now easily import your Instant Articles into AMP (Accelerated Mobile Pages) while retaining the look and feel of your Instant Articles. Follow our guide below on the extension to our Facebook Instant Articles SDK in order to get started.

Instant Articles to AMP

Overview and What You'll Need

1. Download the Facebook Instant Articles Extensions Library

Facebook Instant Article Extensions library is open source, written in PHP. It uses the Facebook Instant Articles SDK to convert an Instant Article to AMP. Download the Instant Articles Extensions Library on Github and follow the steps provided. Learn more below.

2. Make Sure You Are Using PHP 5.4 or higher

You must be using PHP 5.4 or higher to convert your articles.

3. Export Your Instant Articles Style

Export the .json file that you use in Instant Articles. Learn more about how to do this below.

4. Make Sure You Are Publishing Valid Instant Articles.

If you're not already publishing Instant Articles, check out our Getting Started guide.

5. Media Element and Metadata Formatting Options

Learn about sizing media elements and inserting metadata for the conversion between Instant Articles and AMP.

Below is an example of what an article should look like in both Instant Articles and AMP formats.

Instant Article:

AMP converted from Instant Article:

Getting Started

Below is a sample Instant Article: "instant-article-example.html" that you can copy and paste.

<!doctype html>
<html lang="en" prefix="op: http://media.facebook.com/op#">
  <head>
    <meta charset="utf-8">
    <!-- URL of the web version of this article -->
    <link rel="canonical" href="http://example.com/article.html">
    <meta property="op:markup_version" content="v1.0">
  </head>
  <body>
    <article>
      <header>
        <!-- The title and subtitle shown in your Instant Article -->
        <h1>Article Title</h1>
        <h2>Article Subtitle</h2>

        <!-- The date and time when your article was originally published -->
        <time class="op-published" datetime="2014-11-11T04:44:16Z">November 11th, 4:44 PM</time>

        <!-- The date and time when your article was last updated -->
        <time class="op-modified" dateTime="2014-12-11T04:44:16Z">December 11th, 4:44 PM</time>

        <!-- The authors of your article -->
        <address>
          <a rel="facebook" href="http://facebook.com/brandon.diamond">Brandon Diamond</a>
          Brandon is an avid zombie hunter.
        </address>
        <address>
          <a>TR Vishwanath</a>
          Vish is a scholar and a gentleman.
        </address>

        <!-- The cover image shown inside your article --> 
        <figure>
          <img src="http://mydomain.com/path/to/img.jpg" />
          <figcaption>This image is amazing</figcaption>
        </figure>   

        <!-- A kicker for your article --> 
        <h3 class="op-kicker">
          This is a kicker
        </h3>        

      </header>

      <!-- Article body goes here -->

      <!-- Body text for your article -->
      <p> Article content </p> 

      <!-- A video within your article -->
      <figure>
        <video>
          <source src="http://mydomain.com/path/to/video.mp4" type="video/mp4" />
        </video>
      </figure>

      <!-- An ad within your article -->
      <figure class="op-ad">
        <iframe src="https://www.adserver.com/ss;adtype=banner320x50" height="60" width="320"></iframe>
      </figure>

      <!-- Analytics code for your article -->
      <figure class="op-tracker">
          <iframe src="" hidden></iframe>
      </figure>

      <footer>
        <!-- Credits for your article -->
        <aside>Acknowledgements</aside>

        <!-- Copyright details for your article -->
        <small>Legal notes</small>
      </footer>
    </article>
  </body>
</html>

Converting Instant Articles to AMP code example:

// Load instant article file into string
$instant_article_string = file_get_contents('instant_article_example.html');

// Converts it into AMP
$amp_string = AMPArticle::create($instant_article_string)->render();

Get the full example code here.

Styling Your AMP Articles

Exporting Your Style from Instant Articles

You can generate your AMP document while retaining the look and feel of your Instant Articles. Any personalized color, font size and spacing will be applied to your AMP documents. The Instant Articles style name of your choice will be what's used when converting to AMP.

Follow these steps to export your style:

  1. Open "Publishing Tools" from your Facebook Page.
  2. Select "Configuration" from the left menu.
  3. Under "Tools" select the "Styles" section.
  4. Select the style you want to use in your AMP articles.
  5. On the right side of your style name, on the top of the page, click on the down arrow to see the full menu.
  6. Select the "Export" option. This will start the file named ".style.json"

Inserting Your Style in the Converter

You need to save the exported style file to your filesystem — in this example we are setting this under the /articles/styles folder.

The Instant Article to AMP conversion uses the same style you created in the Style Editor. Every Instant Article sets a style using the code below:

<meta property="fb:article_style" content="wod-expert"/>

When converting an Instant Article with the style named "wod-expert" the converter will look for an exported style configuration with name "wod-expert.style.json" by default.

Click here to see an example style named "wod-expert.style.json" exported from the style editor.

The example file above will be used in the converter to generate the same look and feel of your Instant Articles into AMP.

// Load instant article file into string
$instant_article_string = file_get_contents(__DIR__.'/instant-article-example.html');
$properties = array(
  'lang' => 'en-US',                   // You can set the language your article have
  'styles-folder' => __DIR__.'/styles' // Where the styles are stored
);

/*
  As the name of the style used within the Instant article refers to <code>"gray"</code>, the 
  file within directory <code>/styles</code> will be the <code>/styles/gray.style.json.</code>
*/

// Converts it into AMP
$amp_string = AMPArticle::create($instant_article_string, $properties)->render();

You can see working examples here.

Overriding the Style Configuration for AMP Articles

The conversion class allows you to override converted Instant Articles styles for AMP through a simple 'override-styles' property as shown in the example below:

// Load instant article file into string
$instant_article_string = file_get_contents(__DIR__.'/instant-article-example.html');

// Loads the exported style from a custom path.
$styleGotFromSomewhereElse = file_get_contents(__DIR__.'/styles/style-got-from-somewhereelse.json');

$properties = array(
    // You can set the language your article have
    'lang' => 'en-US',

    // Where the styles are stored
    'styles-folder' => __DIR__.'/styles',

    // Overrides any style linked from the Instant Article, this might be useful if you want to apply same style to all your Instant Articles.
    'override-styles' => $styleGotFromSomewhereElse
);

/*
  As the name of the style used within the Instant article refers to <code>"gray"</code>, the
  file within directory <code>/styles</code> will be the <code>/styles/gray.style.json.</code>
*/

// Converts it into AMP
$amp_string = AMPArticle::create($instant_article_string, $properties)->render();

Media Elements Sizing

In Instant Articles the size of media elements are automatically scaled to the size of the phone. For some layouts in AMP, however, the AMP HTML Specification requires some media elements (amp-img and amp-video) to specify their width and height. To make sure your photos and videos appear the correct size, the SDK Extension provides multiple ways to create the correct size.

  • By default all images and videos will be 380px x 240px.
  • If properties “default-media-width” and/or “default-media-height” are specified, then their values will replace the default values above.
  • Note: For images, the current implementation will scale the height values based in the specified aspect ratio while the width will be unchanged (380px).

Examples

An image as it would appear in an Instant Article document:

<img src="http://www.site.com/images/logo.jpg" />

Without manually inputting additional sizing, this is how it will appear for AMP.

<amp-img src="http://www.site.com/images/logo.jpg" width="380" height="240" />

If sizing control properties, shown below, are specified

$properties = array(
  'default-media-width' => 760,
  'default-media-height' => 100,
  ...
);

The expected output for AMP is:

<amp-img src="http://www.site.com/images/logo.jpg" width="380" height="50" />

Note that the width was kept at 380px but the height was scaled to 50% of the specified value (100px) to keep the aspect ratio defined as 760px x 100px.

The width is kept to get full screen width, matching screen devices on the portrait orientation.

Advanced Media Element Sizing

For more granular control of media element sizes the following options are available:

If property “media-sizes” is passed with mappings of URLs to sizes, then the sizes will be used for those elements matched by the “src” attribute. Please see the examples below for more details about the expected format. This is the recommended option if your article contains videos.

If property “media-cache-folder” is passed with the path to a local folder then the converter will look for files with the same name as the name specified in the “src” attribute of the element. If a file is found, then the size of this file will be used for the AMP element. This option is recommended if most of the images used in the article are stored in a local folder.

  • Note: This option is only supported for images.

If property “enable-download-for-media-sizing” is passed with TRUE as value, then the library will attempt to download the resources from the URL defined by the “src” attribute, and then will attempt to determine its size. If your article has videos then this option is only recommended if all the video sizes are specified using the “media-sizes” property. If the latter property is not present or if any of the videos does not have a defined size, then the library will attempt to download a potentially large file.

Examples

If we use the following elements as the source of our transformation:

<img src="http://www.site.com/images/logo.jpg" />
...
<img src="http://www.site.com/images/image.jpg" />
...
<video>
  <source src="https://www.site.com/videos/intro.mov" type="video/mov" />
</video>

with the following properties, shown below, specified:

$properties = array(
  'media-sizes' => array(
    'https://www.site.com/videos/intro.mov' => array(
      320, // width
      240 // height
    )
  ),
  'media-cache-folder' => '~/articles/images',
  'enable-download-for-media-sizing' => TRUE
);

with the following conditions:

  • File at “~/articles/images/logo.jpg” exists and its size is 570px x 100px.
  • Image at "http://www.site.com/images/image.jpg" exists and its size is 1,520px x 800px.

then the expected transformed elements are:

<amp-img src="http://www.site.com/images/logo.jpg" width="380" height="67" />
...
<amp-img src="http://www.site.com/images/image.jpg" width="380" height="200" />
...
<amp-video src="https://www.site.com/videos/intro.mov" width="320" height="240" />

Explanation

  • The first image is matched by name (logo.jpg) to the local file, and its size is used to scale down the height from 100px to 67px (67% of the original size.)
    • Note: Values provided for illustrational purposes only. In this case a local image already scaled to 380px x 67px is recommended.
  • Since the second image is not in the local folder, then it is downloaded and its dimensions are used to scale down the height from 800px to 200px (25% of the original size.)
    • Note: Values provided for illustrational purposes only. In this case a local image already scaled to 380px x 200px is recommended.
  • The video URL is matched in the “media-sizes” property and the provided size (320px x 240px) is used without scaling.

Discovery Metadata

As part of the conversion process the SDK Extension builds an article metadata object to make your content discoverable by search engines.

Several metadata fields are generated form the source Instant Article, but if you want to make your AMP content fully compatible with the Google Search news carousel then you need to provide the optional property 'publisher' with all the Article Publisher required fields.

Note: If the 'publisher' property is not provided the article metadata will still be created, but your content will not be eligible to be displayed in the Google Search news carousel.

Example

$properties = array(
  'publisher' => array(
    '@type' => 'Organization',
    'name' => 'WOD Expert',
    'logo' => array(
      '@type' => 'ImageObject',
      'url' => 'http://blog.wod.expert/wp-content/uploads/2017/04/wod-expert-amp-org-logo.png',
      'width' => 600,
      'height' => 60,
    ),
  ...
);

The property above is used to complement the generation of the discovery metadata of an Instant Article:

{  
    "@context": "http://schema.org",
    "@type": "NewsArticle",
    "mainEntityOfPage": "http://blog.wod.expert/very-first-wod/",
    "headline":"Very First WOD!",
    "datePublished": "2016-05-10T18:05:36+00:00",
    "description": "The first WOD we never forget!",
    "dateModified":"2017-03-17T16:46:07+00:00",
    "author": {  
      "@type": "Person",
      "name": "\u00c9verton Ros\u00e1rio"
  },
    "image":{  
      "@type": "ImageObject",
      "url": "http://blog.wod.expert/wp-content/uploads/2017/03/fail1.jpg",
      "width": 800,
      "height": 454
  },
    "publisher": {  
      "@type": "Organization",
      "name": "WOD Expert",
      "logo": {  
        "@type": "ImageObject",
        "url": "http://blog.wod.expert/wp-content/uploads/2017/04/wod-expert-amp-org-logo.png",
        "width": 600,
        "height": 60
    }
  }
}

Architecture of the Converter

Facebook Instant Articles SDK has a well defined element structure that guarantees valid Instant Article markup, making it possible to convert Instant Article elements into AMP components.

Converting Steps

  1. Parse Instant Article document
  2. Traverse Instant Article Element Structure
  3. Use Styling content from Style Editor and convert it into valid AMP CSS
  4. Fill in AMP format using the correct AMP Components for each Instant Article component

Parse Instant Article Document

Parsing consists into loading a valid Instant Article document into the Element structure from Facebook Instant Articles SDK, since it guarantees:

  • Valid Instant Article structure
  • Easy to traverse structure
  • Conversion and validation structures
  • Exposes article info (like texts, titles, URLs etc)

Code snippet to parse a valid Instant Article document

$parser = new Parser();
$document = new \DOMDocument();
$document->loadHTML($html_file);
$instant_article = $parser->parse($document);

Traverse Instant Article Elements Structure

Traversing the Instant Article Elements Structure is a easy task, since all the info is accessible through the getter methods. The root element is the InstantArticle element.

Header info: (see more about getters within Header.class)

$header = $instant_article->getHeader();
$title = $header->getTitle();
$subtitle = $header->getSubtitle();
$authors = $header->getAuthors();
...

Footer Info: (see more about getters within Footer.class)

$footer = $instant_article->getFooter();
$credits = $footer->getCredits();
$copyright = $footer->getCopyright();
$relatedArticles = $footer->getRelatedArticles();

Body info: (see more about getters within InstantArticle::getChildren())**

Children within the InstantArticle can be any of these:

$children = $instant_article->getChildren();
foreach ($children as $child) {
    if (Type::is($child, Ad::getClassName())) {
        // Do something to ADs...
    }
}

Creating more extensions

The Instant Articles to AMP converter is extensible and it is possible to override specific conversions for custom components including Ads and Analytics, which have different specifications on Instant Articles and AMP.

In order to override values during the conversion, you should use the Observer class to add filters. The following example will override the URL of all Ads during the conversion:

// Load instant article file into string
$instant_article_string = file_get_contents('instant_article_example.html');

// Create AMPArticle converter
$amp_article = AMPArticle::create($instant_article_string);

// Use a filter to change the URL of Ads
$amp_article->getObserver()->addFilter(
    'IA_AD',
    function ($ad) {
        $ad->setAttribute('src', 'http://...');
        return $ad;
    }
);

// Convert article to AMP
$amp_string = $amp_article->render();

The value you return from the filter will override the value read from the Instant Article. The first parameter your filter function receives is always the original value being filtered.

You can set several filters for the same tag, and these filters will run according to the priority you set. A filter with higher priority will run after filters of lower priority.

In this next example, the first filter will actually run after the second filter because it has a higher priority:

// First filter
$amp_article->getObserver()->addFilter(
    'IA_AD',
    function ($ad) {
        ...
    },
    10 // priority
);

// Second filter
$amp_article->getObserver()->addFilter(
    'IA_AD',
    function ($ad) {
        ...
    },
    9 // priority
);

Some filters will provide you extra parameters for you to use on your functions. If you want to receive extra parameters it is necessary to specify on the fourth parameter of the addFilter method the number of parameters your function wants to receive:

// Filter with extra parameters
$amp_article->getObserver()->addFilter(
    'IA_DOCUMENT',
    function ($ad, $context) {
        ...
    },
    10, // priority
    2 // number of parameters of the provided filter
);

Filter reference

These are the filters available for you to use:

AMP_DOCUMENT

Runs after the AMP content is built, but before the DOMDocument is serialized to a string.

Usage:

$amp_article->getObserver()->addFilter(
    'AMP_DOCUMENT',
    function (
        /*DOMDocument*/ $amp_document,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    2
);

AMP_HEAD

Runs after the AMP <head> is built.

Usage:

$amp_article->getObserver()->addFilter(
    'AMP_HEAD',
    function (
        /*DOMElement*/ $amp_head,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    2
);

AMP_BODY

Runs after the AMP <body> is built.

Usage:

$amp_article->getObserver()->addFilter(
    'AMP_BODY',
    function (
        /*DOMElement*/ $amp_body,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    2
);

AMP_HEADER

Runs after the AMP <header> is built.

Usage:

$amp_article->getObserver()->addFilter(
    'AMP_HEADER',
    function (
        /*DOMElement*/ $amp_header,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    2
);

AMP_ARTICLE

Runs after the AMP <article> is built.

Usage:

$amp_article->getObserver()->addFilter(
    'AMP_ARTICLE',
    function (
        /*DOMElement*/ $amp_article,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    2
);

AMP_FOOTER

Runs after the AMP <footer> is built.

Usage:

$amp_article->getObserver()->addFilter(
    'AMP_FOOTER',
    function (
        /*DOMElement*/ $amp_footer,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    2
);

IA_PARAGRAPH

Runs for every Paragraph element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_PARAGRAPH',
    function (
        /*DOMElement*/ $amp_paragraph,
        /*Facebook\InstantArticles\Elements\Paragraph*/ $ia_paragraph,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_BLOCKQUOTE

Runs for every Blockquote element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_BLOCKQUOTE',
    function (
        /*DOMElement*/ $amp_blockquote,
        /*Facebook\InstantArticles\Elements\Blockquote*/ $ia_blockquote,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_H1

Runs for every H1 element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_H1',
    function (
        /*DOMElement*/ $amp_h1,
        /*Facebook\InstantArticles\Elements\H1*/ $ia_h1,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_H2

Runs for every H2 element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_H2',
    function (
        /*DOMElement*/ $amp_h2,
        /*Facebook\InstantArticles\Elements\H2*/ $ia_h2,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_LIST

Runs for every List element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_LIST',
    function (
        /*DOMElement*/ $amp_list,
        /*Facebook\InstantArticles\Elements\List*/ $ia_list,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_PULLQUOTE

Runs for every Pullquote element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_PULLQUOTE',
    function (
        /*DOMElement*/ $amp_pullquote,
        /*Facebook\InstantArticles\Elements\Pullquote*/ $ia_pullquote,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_IMAGE

Runs for every Image element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_IMAGE',
    function (
        /*DOMElement*/ $amp_image,
        /*Facebook\InstantArticles\Elements\Image*/ $ia_image,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_GIF

Runs for every GIF element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_GIF',
    function (
        /*DOMElement*/ $amp_gif,
        /*Facebook\InstantArticles\Elements\GIF*/ $ia_gif,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_VIDEO

Runs for every Video element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_VIDEO',
    function (
        /*DOMElement*/ $amp_video,
        /*Facebook\InstantArticles\Elements\Video*/ $ia_video,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_AUDIO

Runs for every Audio element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_AUDIO',
    function (
        /*DOMElement*/ $amp_audio,
        /*Facebook\InstantArticles\Elements\Audio*/ $ia_audio,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_SLIDESHOW

Runs for every Slideshow element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_SLIDESHOW',
    function (
        /*DOMElement*/ $amp_slideshow,
        /*Facebook\InstantArticles\Elements\Slideshow*/ $ia_slideshow,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_INTERACTIVE

Runs for every Interactive element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_INTERACTIVE',
    function (
        /*DOMElement*/ $amp_interactive,
        /*Facebook\InstantArticles\Elements\Interactive*/ $ia_interactive,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_MAP

Runs for every Map element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_MAP',
    function (
        /*DOMElement*/ $amp_map,
        /*Facebook\InstantArticles\Elements\Map*/ $ia_map,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_ANALYTICS

Runs for every Analytics element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_ANALYTICS',
    function (
        /*DOMElement*/ $amp_analytics,
        /*Facebook\InstantArticles\Elements\Analytics*/ $ia_analytics,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

IA_AD

Runs for every Ad element found on the Instant Article document.

Usage:

$amp_article->getObserver()->addFilter(
    'IA_AD',
    function (
        /*DOMElement*/ $amp_ad,
        /*Facebook\InstantArticles\Elements\Ad*/ $ia_ad,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

GET_GOOGLE_MAPS_KEY

Allows overriding the Google API key for converting the maps.

Usage:

$amp_article->getObserver()->addFilter(
    'GET_GOOGLE_MAPS_KEY',
    function (
        /*string*/ $google_api_key,
        /*array*/ $properties,
        /*AMPContext*/ $context
    ) {
        // filter code
    },
    10,
    3
);

DEFAULT_LOGO_HEIGHT

Allows overriding the default logo height.

Usage:

$amp_article->getObserver()->addFilter(
    'DEFAULT_LOGO_HEIGHT',
    function (
        /*int*/ $default_logo_height,
    ) {
        // filter code
    },
    10,
    1
);

DEFAULT_LOGO_WIDTH

Allows overriding the default logo width.

Usage:

$amp_article->getObserver()->addFilter(
    'DEFAULT_LOGO_WIDTH',
    function (
        /*int*/ $default_logo_width,
    ) {
        // filter code
    },
    10,
    1
);

AMP_GETMETADATAIMAGE

Runs after the Schema.org image meta-data JSON is generated.

Usage:

$amp_article->getObserver()->addFilter(
    'AMP_GETMETADATAIMAGE',
    function (
        /*array*/ $image_metadata,
        /*array*/ $properties,
    ) {
        // filter code
    },
    10,
    2
);

AMP_GETPUBLISHER

Runs after the Schema.org publisher meta-data JSON is generated.

Usage:

$amp_article->getObserver()->addFilter(
    'AMP_GETPUBLISHER',
    function (
        /*array*/ $publisher,
        /*array*/ $properties,
    ) {
        // filter code
    },
    10,
    2
);