Editor Integration

You can use the Shutterstock Editor integration to spawn the editor application in a container on a partner page. For a live demo, see JS bin.

First, include the library file on the parent page:

<script src="https://s.picdn.net/editor/image/assets/integration.js"></script>

This library exposes an Editor property on the parent window, which accepts a configuration object:

const editor = window.Editor({
    ...options
});

Example

This example code launches an instance of Editor with the default options.

<script src="https://s.picdn.net/editor/image/assets/integration.js"></script>
<script>
    window.Editor({
      apiKey: EDITOR_API_KEY
    }).launch();
</script>

API keys

The Editor instance requires an API key. For testing purposes, you can use the key XDj5YViial3ibnnevAfmGi14, which works only for local testing (when the URL starts with http://localhost). To request an API key so you can host Editor on your own domain, Contact us.

Setting options

With the options object, you can control the dimensions of the Editor instance, the preset image sizes, the starting canvas settings, and other behaviors.

<script src="https://s.picdn.net/editor/image/assets/integration.js"></script>
<script>
var editorInstance = window.Editor({
  apiKey: EDITOR_API_KEY,
  language: 'en',
  image: 'https://www2.shutterstock.com/blog/wp-content/uploads/sites/5/2015/05/volcano-header-1440x960.jpg',
  excludeFeatures: ['filters'],
  initialPanel: 'text',
  logo: true,
  logoUrl: 'https://www2.shutterstock.com/base/public/images/logos/logo-shutterstock-de64a370ef.png',
  primaryActionText: 'Save and close',
  container: document.querySelector('#editor'),
  canvas: {
    height: 500,
    width: 500,
    fill: 'rgb(62, 69, 79)'
  },
  presets: [{
    name: 'Icon',
    height: 128,
    width: 128
  }, {
    name: 'Book Cover',
    height: 1600,
    width: 2400
  }],
  onPrimaryAction: function() {
    editorInstance.ui.showActivityIndicator()
    .then(() => editorInstance.getBase64({
      format: 'jpg',
      quality: .91
    }))
    .then((base64) => {
      // do something with the base64 data
    })
    .then(editorInstance.ui.hideActivityIndicator)
    .then(editorInstance.hide);
  }
});

// Launch instance of Editor
editorInstance.launch().then(function(editor) {
  // Editor is loaded and ready for user interaction
}).catch(function(err) {
  // Handle error
});
</script>

Loading external images and logos

You can open an external image in Editor with the image parameter. If you specify a URL to the image, the image must be a JPEG or PNG file and it must have CORS headers set for https://www.shutterstock.com to be able to load the image anonymously within the iframe. Alternatively, you can specify an HTMLImageElement element on the parent page If the page that loads Editor has the proper CORS headers to load the image, Editor inherits the parent page's CORS policy. For example, this code loads the image in the element with the ID image-to-edit:

const editor = Editor({
    apiKey: EDITOR_API_KEY,
    image: document.getElementById('image-to-edit')
});

You can also use a custom logo by specifying the logo URL in the logo parameter. This image must have CORS headers set for https://www.shutterstock.com to be able to load the logo anonymously within the iframe.

Excluding features

You can hide Editor features with the excludeFeatures parameter. To hide features, include one or more of these values in the parameter:

  • preset_size
  • text
  • shapes
  • backgrounds
  • filters
  • uploads
  • search
  • pages

Initial panel

The initialPanel parameter activates a specified panel when the instance opens. You can open the following panels:

  • elements
  • pages
  • preset_size
  • search
  • text

Custom presets

You can configure one or more preset dimensions with the presets parameter. Preset height and width must be between 0 and 12,000 pixels. For example:

presets: [{
    name: 'Icon',
    height: 128,
    width: 128
}, {
    name: 'Book Cover',
    height: 1600,
    width: 2400
}]

Running headless

You can hide the Editor instance from the parent page by setting the hidden parameter to true. All operations are still available, but no user interface appears on the page. You can toggle the appearance with the hide() and show() methods. When the promise returned by .launch() resolves, the Editor instance is ready for user interaction and can be shown, as in the following example:

const editor = Editor({
    apiKey: EDITOR_API_KEY,
    hidden: true,
});
editor
  .launch()
  .then(editor.show);

Options

This table lists the options that you can set on the Editor instance:

ParameterDescriptionTypeExample
apiKey(Required) Partner API keyString'89TuQL0KE6SQJ7bcCZHE12Cm'
containerContainer element to insert Editor intoElement'document.querySelectorAll('#e')'
debugWhether to log developer debugging informationBooleantrue
dpiModeSpecify that the DPI is intended to be a max rather than fixed. If final image size is bigger than limits allowed from hardware/software, the DPI will be reduced automatically.String'auto' or 'normal'
excludeFeaturesList of features to hideArray['filters', ...]
imageThe URL or image data of an image to loadString, ImageData, or HTMLImageElementhttp://example.site/image.jpg
sstkImageIdThe id of a shutterstock image, in alternative to image optionString'3434534345'
initialPanelPanel activated by defaultString'shapes'
hiddenWhether to hide the UI from view (headless)Booleantrue
hideCloseButtonHide or show close buttonBooleantrue
languageISO 639‑1 supported language codeString'de'
logoWhether to show the logoBooleantrue
logoUrlURL to a custom logoStringhttp://example.site/image.jpg
presetsList of preset objectsArray[{name: 'Icon', height: 128, width: 128}]
searchLicenseChoose what types of licenses are allowed to be returned in an image search.String Or Arraycommercial|editorial|enhanced
preventCloseWhether to prevent the editor from closing when the close button is clickedBooleantrue
primaryActionTextPrimary action button text contentString'Download'
showPrimaryActionButtonWhether to show the primary action button (default: true)Booleantrue
primaryActionColorColor of the text of the primaryActionButtonString'red'
primaryActionBackgroundColorColor of the background of the primaryActionButtonString'rgb(255,0,244)'
unitsUnits of the size of the artboard/designString'CENTIMETERS' or 'PIXELS' or 'INCHES'
overrideUploadHandlerOverride the default upload/drop handler of the application with the one specified in the 'fileUpload' eventBooleantrue
canvas
- canvas.fillInitial fill color of the canvasString'#ff0000'
- canvas.widthInitial width of the designInteger500
- canvas.heightInitial height of the designInteger200

Methods

An instance of the Editor library exposes these methods for advanced interactions with the instance:

NameDescriptionReturn value
addBackgroundImage({ image, properties })Adds an image to the canvas as the background, replacing any existing background image. The image field can be a URL string or an ImageData object. The properties field is an optional FabricJS object properties object. Another valid property is the resizeArtboard. If set to true the canvas size will be changed to fit the image. If set to false, the image will be scaled to cover the actual canvas size.Promise<Object> of FabricJS properties for the image
addImage({ image, properties })Adds an image to the canvas. The image field can be a URL string or an ImageData object. The properties field is an optional FabricJS object properties object.Promise<Object> of FabricJS properties for the image
addShutterstockImage({ id, properties })Adds an image to the canvas. The id field is a string and is an ID from the shutterstock library. The properties field is an optional FabricJS object properties object.Promise<Object> of FabricJS properties for the image
clear()Removes all of the elements on the canvas.Promise
clearHistory()Remove all undo and redo steps, create a basic step with the current situation. Useful to be called after setDesign or after a combination of clear and addImage in order to do not keep the older history on the applicationPromise
close()Closes and removes the Editor instance.Promise
download({ format, quality, multipage })Renders and downloads the image directly to the user's browser. Accepts an optional option object with format and quality fields. Be careful that calling hide() the promise resolves can make downloads fail. The format field is 'jpg', 'png', or 'webm'. The quality field is a number between 0 and 1. multipage is a boolean that will return all pages in a design when true.Promise
getBase64({ format, quality, multipage })Returns the base64 data for the current design. Accepts an optional option object with format and quality fields. The format field is 'jpg', 'png', or 'webm'. The quality field is a number between 0 and 1. multipage is a boolean that will return all pages in a design when true.Promise<String|[String]>
getBlob({ format, quality, multipage })Returns the blob data for the current design. Accepts an optional option object with format and quality fields. The format field is 'jpg', 'png', or 'webm'. The quality field is a number between 0 and 1. multipage is a boolean that will return all pages in a design when true.Promise<String>
getDesign()Gets the JSON data for the current design.Promise<Object>
getEffectiveDPI()Gets the current DPI for the design. If dpiMode is set to auto the value returned is the dpi of the final download at current size/resolution. If dpiMode is set to normal the result is the DPI the document is set at.Promise<Number>
getImageData()Returns the raw image data for the current design.Promise<ImageData>
getShutterstockAssetIds()Returns an array of shutterstock asset ids used in design.Promise<Array>
hide()Hides the Editor UI from view.Promise
launch()Initializes the Editor instance.Promise
setDesign(json)Sets the current design to the supplied JSON data.Promise
setDPIMode(String)Sets the dpiMode to auto or normal, any value different than auto is considered normalPromise
show()Restores the Editor UI into view.Promise
pages.setMaxPages(numberOfPages)Sets the max number of pages a user is allowed to add. The numberOfPages value is an integer.Promise
ui.disableCanvasSize()Disable the ability for the user to update the size of the canvas.Promise
ui.enableCanvasSize()Enable the ability for the user to update the size of the canvas.Promise
ui.showActivityIndicator()Displays a loading interface over the design areaPromise
ui.hideActivityIndicator()Hides the loading interface displayed by ui.showActivityIndicatorPromise
setOverlayFromSVG(<String>)Set an overlay in the center of the canvas by passing an SVG string. The overlay is unaffected by user input, and does not show up in output renders. In the application, it always appears on top.Promise
setEditorUnits(<String>)Set the units for the artboard/design size. Allowed parameters values are 'PIXELS', 'INCHES', 'CENTIMETERS'Promise

setOverlayFromSVG

A layer on top of a design that won't be affected by user input, an overlay, can be useful in printing use cases. To set an overlay, pass either a string representation of an SVG or a url to an SVG to the setOverlayFromSVG method.

In order for Editor to display the overlay correctly, width and height must be specified as attributes of the <svg> tag.

The SVG will be loaded and positioned at the center of the current artboard / design.

var editorInstance = window.Editor({
  ...
});

editorInstance.setOverlayFromSVG('<svg width="1000px" height="1500px"><rect width="990px" height="990px" x="5px" y="5px" /></svg>');
// or alternatively
editorInstance.setOverlayFromSVG('http://www.link.to/mySvgFile.svg');

// to hide the overlay
editorInstance.setOverlayFromSVG('');

Events

You can add listeners to events that happen in the Editor instance in these ways:

  • You can add the listeners to the options object:
var editorInstance = window.Editor({
  apiKey: EDITOR_API_KEY,
  language: 'en',
  image: 'https://www2.shutterstock.com/blog/wp-content/uploads/sites/5/2015/05/volcano-header-1440x960.jpg',
  onClose: function() {
    | alert('Goodbye');
  },
  onPrimaryAction: function() {
    editorInstance.getBase64({
      format: 'jpg',
      quality: .91
    }).then(function(base64) {
      // Handle base64 image data
      editorInstance.hide();
    });
  }
});
  • You can add listeners to the instance after you initialize it:
editorInstance.on('close', function() {
  alert('Goodbye.');
});

This table lists the events to which you can add listeners:

NameDescriptionSupplied data
closeCalled when the Editor instance is closednone
errorCalled when an error occursError data
hideCalled when the Editor instance is hiddennone
openCalled when the Editor instance is openednone
primaryactionCalled when the primary action button is activatednone
showCalled when the Editor instance is shownnone
designChangedCalled when the user changes the designnone
fileUploadCalled when uploads or drags an image from their computer into Editornone

Handling upload events

When a user drags a valid file or clicks on File -> Upload images in Editor, a fileUpload event is triggered. If a user selects or drags in more than one image at once, each of them will trigger an event.

The event callback receives the following information:

{
    filename: String, // the name of the file dragged or uploaded
    fileObj: File, // the file object derived from the native event https://developer.mozilla.org/en-US/docs/Web/API/File
}

If the Editor integration is configured with overrideUploadHandler: true, typical behavior – automatically adding the image to the canvas – will be prevented. However, developers can still add it to the canvas using the fileObject.

In this example we read the fileObj as a dataURL and we add back an image using that dataUrl as a source.

editor = Editor({
    apiKey: ...,
    initialPanel: 'text',
    primaryActionText: 'Custom Text',
    onClose() {
      editor = null;
    },
    onFileUpload(data) {
      const reader = new FileReader();
      reader.onload = function () {
        const dataURL = reader.result;
        editor.addImage({ image: dataURL });
      };
      reader.readAsDataURL(data.fileObj);
    },
  });

This differs from the default application behavior (i.e. if overrideUploadHandler were false) in that the dataURL value contains the entirety of the image data, and will be embedded in the design object. This simultaneously enables the design to be stored and increases the size of the design by the size of the image. This may very well increase the size of the design JSON to an impractical size.

A preferred method of handling image upload events is for developers to upload image data themselves, generating a traditional URL pointing to the image. This image can then be added to the design using the addImage method, resulting in a design that can be retrieved later that is not larger than needed. Image resources will need to respect CORS rules.

Licensing images when using Shutterstock Editor

You may want to allow users to search for and license images directly through Editor. This is supported, provided that you are able to perform licensing yourself through the API<sup>*</sup>.

Once a user has created their design, the following workflow enables rendering a design that contains Shutterstock assets:

  1. User clicks on the primary action button.
  2. The primaryaction event is fired, with an array of Shutterstock image IDs included.
  3. License the images included in that array, retrieving URLs to unwatermarked assets.
  4. When calling getBase64 or getBlob or getImageData, pass a property called gatekeeperUrls, consisting of an array of objects passing the unwatermarked URLs obtained in the previous step, in the first argument. See "gatekeeperUrls" below for more info.
editor.on('primaryaction', ({ imageIds }) => {
  // imageIds, a property of the first argument of the data
  // supplied by the primary action event, is an array of
  // image IDs present in the design.

  yourExternalLicensingLogic(imageIds)
    .then(gatekeeperUrls => {
      const renderMetadata = { gatekeeperUrls };
      return editor.getBase64(renderMetadata);
    })
    .then(imageData => {
      // Licensing is complete, with imageData representing the result of the `getBase64` call.
    });
});

gatekeeperUrls

The gatekeeperUrls property that you pass to getBase64 as a property of the first argument should be an array of objects with id and url properties, such as:

const renderMetadata = {
  gatekeeperUrls: [
    {
      id: 123, // example Shutterstock image ID
      url: 'https://...' // example URL returned from a licensing call
    }
  ]
};
editor.getBase64(renderMetadata);

A functioning demo of the licensing process is located here.

<span><sup>*</sup></span> One important reason that this action needs to be performed through the Shutterstock API and not through the Shutterstock Editor SDK is that your SDK key is not as secure, and not as privileged, as your Shutterstock API key. This ensures that you, and not your end-users, are ultimately in control of licensing.

Dealing with CORS (Cross Origin Resource Sharing)

In order to import images into the Editor canvas, the images need to be able to added to the canvas in an "untainted" fashion. Tainting the canvas with a non-CORS image prevents Editor from functioning correctly. In order to prevent the canvas from being tainted, images loaded into Editor must be one of the following:

  • If you load the image via a URL, the web server that hosts the image must include CORS headers (and respond to the OPTIONS HTTP verb) that allow www.shutterstock.com to load the image cross-origin.
  • If you load the image in an element on the parent page and pass it to the Editor instance either by using the image option at Editor launch or by calling the addImage() or addBackgroundImage() methods, the image must still not trigger a tainted canvas. In this case, you must either serve the image from the same host name as the parent page or serve the image from a server with the corresponding CORS headers and load the image in an <img> tag with the crossorigin attribute configured.

For example, the following code extracts the image data from an image element for use with the addImage and addBackgroundImage methods:

// Create an image
const img = document.createElement('img');
img.src = someUrlToLoadImage;
img.crossOrigin = 'anonymous'; // Or 'use-credentials'
img.onload = () => {
  // Create an empty canvas element
  const canvas = document.createElement('canvas');
  // Match the canvas dimensions to the image dimensions
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;

  // Copy the image contents to the canvas
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

  let imageData;
  try {
    // Get the ImageData from the canvas
    imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  } catch (e) {
    // example of errors would be CORS issues
    console.error(e);
  }
  // Use the imageData to add an image to Editor
  editor.addImage({ image: imageData });
};

Preloading Editor

Depending on network and other factors, Editor takes 6-20 seconds to load. To improve performance, you can load a hidden Editor instance on page load or during idle time and then show the instance when it is needed. The following code loads the Editor instance but does not show it until the showEditor() method is called.

let editor;

// At page load or idle
function initEditor() {
  editor = new Editor({
    ...options,
    hidden: true,
  });

  return editor.launch();
}

// at some point in the future....
function showEditor() {
  editor.show();
}

Limitations and requirements

Browser and operating system support for embedded Editor instances matches that of the core Editor application:

  • Windows (modern versions)
  • OS X (modern versions)
  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Internet Explorer 11
  • Microsoft Edge

Designs exported from Shutterstock Editor must not exceed 12,000 x 12,000 pixels in dimension.

Foreign language support

Editor supports all languages supported by Shutterstock. See https://www.shutterstock.com for a full list of languages.