Back to top

Sources

You can create your own Storify source to make it easy to search for content coming from your service and drag and drop it into a Storify story.

The best way is probably to start from a template and adapt it to your needs.

Here is the source definition for Google

{
    "name": "google",
    "label": "Google",
    "icon": "http://storify.com/public/img/sources/google-24px.png",
    "href": "http://www.google.com",
    "defaults": {
      "results": {
        "loadMore": {
          "attribute":"start",
          "inc":8
        },
        "array": "json.responseData.results",
        "mapping": {
            "type": "link",
            "permalink": "{{result.unescapedUrl}}",
            "posted_at": "{{result.publishedDate}}",
            "data": {
                "link": {
                    "href": "{{result.unescapedUrl}}",
                    "title": "{{result.titleNoFormatting}}",
                    "description": "{{result.content}}",
                    "thumbnail": "{{result.thumbnail}}"
                }
            },
            "attribution": {
                "name": "{{result.visibleUrl}}"
              , "href": "{{result.unescapedUrl}}"
            },
            "source": {
                "name": "{{result.visibleUrl}}"
              , "href": "{{result.unescapedUrl}}"
            },
            "meta": "{{result}}",
            "details_endpoint": "embedly"
        }
      },
      "details": {
        "mapping": "oembed"
      }
    },
    "tabs": [
        {
            "name": "search",
            "label": "Web",
            "api_endpoint": "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=8&q={{formdata.query}}"
        },
        {
            "name": "news",
            "label": "News",
            "api_endpoint": "http://ajax.googleapis.com/ajax/services/search/news?v=1.0&rsz=8&q={{formdata.query}}"
        },
        {
            "name": "images",
            "label": "Images",
            "api_endpoint": "http://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&q={{formdata.query}}",
            "results": {
              "mapping": {
                "type":"image",
                "data":{
                  "image": {
                    "src":"{{result.url}}",
                    "href":"{{result.originalContextUrl}}"
                  }
                }
              }
            }
        }
    ]
}

How it works

Here is the general idea: the source definition is defining all the properties of the source;

  • the different tabs;
  • the form for each tab if any,
  • the api endpoint to call for each tab once the form is submitted with some values and;
  • a mapping table to normalize the results provided by the api endpoint.

Once the object is dropped onto the story, another optional api endpoint is called to retrieve extra information about the object.

Source properties

No need to explain what name, label, href and icon do. The first thing to understand is that your source is divided in different tabs. The defaults property is here to avoid having to specify the same properties that are common among all your tabs. In other words, source.tabs[0].results = source.tabs[0].results || source.defaults; (therefore, if you specify a property both in the defaults and in a tab definition, the latter will take precedence).

Tabs properties

source.tabs = [ {tabObject} ];

The tabs property is an array of tab objects. A tab is an object with the following properties: name, label, api_endpoint, form, results, details.

tab.form

By default, every tab gets a search box that can be accessed in any property with {{formdata.query}} (e.g. in the example above: tab.api_endpoint = "http://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&q={{formdata.query}}".

If you don't want to have any form, you need to explicitly say it using the following value: tab.form = { "_default": [] };. In which case, the api_endpoint will be directly called as soon as the tab becomes active.

You can also customize the form, e.g.

"form": {"_default": [{"type": "searchquery", "name":"username", "placeholder": "Enter a App.net username"}]}

In which case, the value of username can be retrieved later on using {{formdata.username}}.

Or you can add advanced options to your form (like we do for the YouTube source):

"form": {
  "advanced":[
            {"label":"Order by","type":"select", "name":"orderby", "options":[
              {"relevance":"relevance"},
              {"published":"published"},
              {"viewCount":"views"},
              {"rating":"rating"}
              ]
            }
          ]},

This will generate the following form: YouTube advanced form

tab.api_endpoint

This is your api endpoint that will be called when the form is submitted with some values. You can construct the url using the data submitted in the form using a moustache-like syntax: {{formdata.fieldname}}.

E.g.

"api_endpoint": "http://gdata.youtube.com/feeds/api/videos?v=2&max-results=20&alt=jsonc&orderby={{formdata.orderby}}&q={{formdata.query}}"

tab.results

This is an important property. It basically defines how Storify should process the results sent by your api_endpoint. It requires 3 attributes: array, loadMore and mapping.

  • array: where in the json is the array with the results? E.g. json.data.items. Thanks to this, you can subsequently refer to the current result with the result object. E.g. {{result.url}}.
  • loadMore: defines the paging system to load more results
  • mapping: defines the mapping between the result object and a normalized Storify element

tab.results.loadMore

We support two types of pagination: page based and cursor based.

Page based:

"loadMore": {
    "attribute": "start-index",
    "inc": 20,
    "start": 1 // optional, default: 0
  },

This will append to the api_endpoint the parameter start-index, e.g. api_endpoint?start-index=1, then api_endpoint?start-index=21, …

Cursor based:

"loadMore": {
  "cursor": "{{json.cursor.next}}",
  "attribute":"cursor"
},

This will append to the api_endpoint the parameter cursor with value defined in the result json by {{json.cursor.next}}. Sometimes your api_endpoint does not return the cursor value and you need to use a value out of the latest result. You can do that by referring to result instead of json, e.g. {{result.timestamp}}.

tab.results.mapping

This is the most important piece in your source definition. You need to define the mapping between the data returned by your API and the normalized Storify element.

Common attributes for all element types:

type: "link" | "quote" | "image" | "video",
permalink: "unique url to that object on the source website, e.g. {{result.url}}",
posted_at: "Javascript parseable timestamp of the date the object has been created on the source service, e.g. {{result.created_at}}",
attribution: {
  "name" : "Name displayed for attribution, e.g. {{result.author.name}}",
  "href" : "URL the attribution will link to, e.g. {{result.author.profilepage}}"
},
details_endpoint: "api_endpoint to get the details of that object"

The only attribute that depends on the element.type is data:

link

data: {
  link: {
         title: "link title",
         description: "link description",
         thumbnail: "absolute url to thumbnail",
         href:  "link url"
  }
}

quote

data: {
  quote: {
         text: "text of the quote"
  }
}

image

data: {
  image: {
         src: "image absolute url, e.g. {{result.src}}",
         href:  "image link"
  }
}

video

data: {
  video: {
         title: "video title",
         description: "video description",
         thumbnail: "absolute url to thumbnail",
         src:  "video url to be loaded in an iframe"
  }
}

tab.details (optional)

If you define a details attribute to your tab (or to your defaults at the source level, which would apply to all your tabs), then when one of the results is dropped by the user onto a story, we trigger a new api call to fetch detailed data about that object. That can also be used as a mechanism to track the number of times one of your object is added to a story.

details works the same way as the results attribute. You have to specify a api_endpoint and an array attribute to specify where in the returned json the details of the object is (e.g. json.data). You will also need to specify the mapping table if different from the one used to map results. Alternatively, you can just specify the string "oembed" in which case we will use the standard oembed format to deduce the mapping.

If you omit details, we will fall back on embed.ly to get the maximum of information about the object. That's how for example, we can easily turn a soundcloud permalink into a soundcloud embed.

Testing your source

There are two ways you can test your source before submitting it to Storify.

  1. Open the Storify editor
  2. Open the Javascript console
  3. Enter s.editor.sourceManager.loadSourceFromURL(AbsoluteURLToYourSourceDefinitionInJSONP);. Alternatively, you can use the method s.editor.sourceManager.loadSourceFromJSON(YourSourceDefinitionInJSON);.
  4. If everything worked well, you should see your new source in the editor

Submit your source

Just email [email protected] with your source attached and your api_key (request an api_key).