Editor Integration

Using the Shutterstock Editor integration, you can easily add the editor application (Editor) to a partner's website. A live, editable demo of Editor can be found here.

Ready to get started? First, let's include the library on our page:

<!DOCTYPE html>
<html>
  <head>
    <title>Welcome to Editor</title>
  </head>
  <body>
    <script src="https://www.shutterstock.com/editor/image/assets/integration_next.js"></script>
  </body>
</html>

The library exposes an Editor property on the window. Using that property, we can configure an instance of Editor:

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

Now we can launch our instance with the default configuration:

<!DOCTYPE html>
<html>
  <head>
    <title>Welcome to Editor</title>
  </head>
  <body>
    <script src="https://www.shutterstock.com/editor/image/assets/integration_next.js"></script>
    <script>
      const editor = window.Editor({
        apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can only be used for testing purposes
      });
      editor.launch();
    </script>
  </body>
</html>

API keys

The Editor instance requires an API key. For local testing purposes (when the URL you're testing on starts with http://localhost), you can use a test key: XDj5YViial3ibnnevAfmGi14. To request an API key so you can host Editor on your own domain, contact us.

Setting options

By passing options to your instance, you can configure Editor's canvas size, logo, language, starting image, as well as its response to user actions and much, much more. Here are a few of the configurable options:

const options = {
  logo: true, // Whether or not a logo should be displayed
  language: 'en', // One of Editor's 25+ supported languages
  apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can only be used for testing purposes
  primaryActionText: 'Save and close', // The text on the primary action button
  container: document.querySelector('#editor'), // Specify where to insert Editor on the parent page
  logoUrl:
    'https://upload.wikimedia.org/wikipedia/commons/8/81/Wikimedia-logo.svg', // Logo displayed in the Editor
  image:
    'https://upload.wikimedia.org/wikipedia/commons/a/a2/Map_Canada_political-geo.png', // Initial image
  canvas: {
    width: 500,
    height: 500,
    fill: 'rgb(62, 69, 79)', // Canvas color
  },
  presets: [
    // Custom canvas sizes for your users to choose from
    {
      width: 128,
      height: 128,
      name: 'Icon',
    },
    {
      width: 2400,
      height: 1600,
      name: 'Book Cover',
    },
  ],
  onPrimaryAction: () =>
    // Function executed when the primary action button is clicked
    editor.ui
      .showActivityIndicator() // Display a loading screen
      .then(() =>
        editor.getBase64({
          // Get active design as base64 data
          quality: 0.91,
          format: 'jpg',
        })
      )
      .then(console.log) // Log base64 data
      .then(editor.ui.hideActivityIndicator) // Hide loading screen
      .then(editor.hide), // Hide editor
};

const editor = window.Editor(options);

// Launch an Editor instance
editor
  .launch()
  .then((editor) => {
    // Editor is loaded and ready for user interaction
  })
  .catch((err) => {
    // Handle error
  });

Loading external images and logos

You can load an external image in Editor by using the image option. If using an image URL (https://www.shutterstock.com/base/public/images/logos/logo-shutterstock-de64a370ef.png), the image must be a JPEG or PNG file and permit https://www.shutterstock.com cross-origin use. Properly setting the image's CORS headers will enable Editor to load the image anonymously within the iframe. Alternatively, you can use an HTMLImageElement, in which case, Editor will inherit the parent page's CORS policy. As an example, let's use a HTMLImageElement with the ID image-to-edit:

<!DOCTYPE html>
<html>
  <head>
    <title>Welcome to Editor</title>
  </head>
  <body>
    <img
      id="image-to-edit"
      crossorigin="anonymous"
      src="https://upload.wikimedia.org/wikipedia/commons/a/a2/Map_Canada_political-geo.png"
    />
    <script src="https://www.shutterstock.com/editor/image/assets/integration_next.js"></script>
    <script>
      const editor = window.Editor({
        apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can only be used for testing purposes
        image: document.getElementById('image-to-edit'),
      });
      editor.launch();
    </script>
  </body>
</html>

A custom logo can be displayed with the logoUrl parameter. As previously mentioned, to be able to anonymously load the logo within the iframe, Editor (https://www.shutterstock.com) must be permitted cross-origin use.

Enabling and disabling features

Editor has an expanding feature set. Some features are enabled by default, while others need to be enabled by the integrator. The following features are enabled by default:

  • text (text elements)
  • search (image search)
  • uploads (user uploads)
  • filters (image filters)
  • opacity (opacity slider)
  • pages (multi-page designs)
  • shadow (shadows on objects)
  • elements (emojis and shapes)
  • canvas_resize (canvas resizing)

Features can be disabled using editor.config.disableFeature(featureName).

The following features are disabled by default. They can be enabled using editor.config.enableFeature(featureName):

  • templates (templates, requires access to Shutterstock's full collection. For more information, contact us)
  • custom_uploads (integrator-provided uploads)
  • similar_images_promo (similar images promotion)
  • custom_templates (integrator-provided templates)
  • custom_text_templates (integrator-provided text templates)
  • background_removal (background removal, found within the Effects panel)
  • background_removal_notification (displayed once per session, a popup showing the user where to find the background removal tool)

Custom presets

You can configure one or more preset dimensions with the presets parameter. A preset has a name, as well as a height and width, which must be between 0 and 12,000, inclusive, pixels:

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

Running headless

You can hide the Editor instance by setting the hidden parameter to true. All operations are still available, but no user interface will appear 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 = window.Editor({
  hidden: true,
  apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can only be used for testing purposes
});
editor.launch().then(editor.show);

Options

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

ParameterDescriptionTypeExampleDefault value
apiKeyPartner API key (required)String'89TuQL0KE6SQJ7bcCZHE12Cm'
containerSpecify where to insert Editor on the pageElementdocument.querySelector('#image-to-edit')'document.querySelector('body')
canvas
canvas.fillInitial fill color of the canvasString'#ff0000'
canvas.heightInitial pixel height of the design (cannot exceed 12000). Additional notes.Integer2001200
canvas.widthInitial pixel width of the design (cannot exceed 12000). Additional notes.Integer5001200
debugLog developer debugging informationBooleantruefalse
dpiModeWhen set to 'auto', the DPI will be automatically adjusted (when necessary) to allow for the creation of images with large physical sizesString'auto' or 'normal''normal'
hiddenHide Editor from viewBooleantruefalse
hideCloseButtonHide Editor's close buttonBooleantruefalse
imageThe URL, image data or element of an image to add to the canvasString, ImageData, or HTMLImageElement'http://example.site/image.jpg'
languageAn ISO 639‑1 supported language codeString'de''en'
licenseRequestCallbackFunction to handle licensing requests from the application (needs to be provided at startup)function({ sstkId, size = 'medium' }) => myLicensingLogic(sstkId)
logoDisplay logoBooleantruetrue
logoUrlThe URL of a custom logoString'http://example.site/image.jpg'
onFileUploadCallback triggered on file upload. To work, overrideUploadHandler needs to be set to true.function({ tempId, fileObj, fileName}) => { ... }
onPrimaryActionCallback triggered when the primaryActionButton is clickedfunction() => editor.ui.showActivityIndicator()
overrideUploadHandlerOverride the application's default upload handler with the one specified in the fileUpload event or onFileUpload optionBooleantruefalse
presetsCanvas size presetsArray[{name: 'Icon', height: 128, width: 128}, {...}]
preventClosePrevent the Editor from closing when the close button is clickedBooleantruefalse
primaryActionTextThe primary action button's textString'Download''Download'
searchLicenseThe type of license used by image search (commercial, editorial and/or enhanced)String or Array['commercial', 'editorial', 'enhanced']
showPrimaryActionButtonShow the primary action buttonBooleantruetrue
sstkImageIdAn alternative to the image option, Shutterstock images can be loaded with their IDString'3434534345'
unitsCanvas size unitsString'CENTIMETERS' or 'PIXELS' or 'INCHES''PIXELS'

Methods

An instance of the Editor library exposes these methods:

NameDescriptionReturn value
addBackgroundImage({ image, properties = { resizeArtboard: true }})Adds a background image to the canvas, replacing any existing background image. The image field can be a URL string or an ImageData object. When properties.resizeArtboard is set to true, the canvas size will be changed to fit the image. When set to false, the image will be scaled to cover the canvas size.Promise<Object> of FabricJS properties for the image
addGroup(json)Add objects to the current design. The objects should be supplied as JSON.Promise
addImage({ image })Adds an image to the canvas. The image field can be a URL string or an ImageData object.Promise<Object> of FabricJS properties for the image
addShutterstockImage({ id })Using a Shutterstock image ID, add the corresponding image to the canvas. The id should be passed as a string.Promise<Object> of FabricJS properties for the image
clear()Remove all of the elements on the canvas.Promise
clearHistory()Remove the user's undo/redo history. Useful for resetting the application's history after actions such as setDesign and clear.Promise
close()Closes and removes the Editor instance.Promise
download({ format, quality, multipage })Renders and downloads the image directly to the user's browser. Accepts optional arguments: format can be either a 'jpg', 'png', 'pdf' or 'tiff'. quality is a number between 0 and 1. multipage is a boolean that will return all pages in a design when set to true. Careful, calling hide() before the promise resolves can cause downloads to fail.Promise
getBase64({ format, quality, multipage })Returns the base64 data for the current design. Accepts optional arguments: format can be either 'jpg' or 'png'. quality is a number between 0 and 1. multipage is a boolean that will return all pages in a design when true.Promise<String>
getBlob({ format, quality, multipage, scaleDownLargePages })Returns the blob data for the current design. Accepts optional arguments: format can be either 'jpg', 'png', 'pdf' or 'tiff'. quality is a number between 0 and 1. multipage is a boolean that will return all pages in a design when true. scaleDownLargePages, which is only valid for pdf, will reduce the file's physical dimensions to 1/10 when it is bigger than 200 inches.Promise<Blob>
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 download at the current size/resolution. If dpiMode is set to normal, the value returned is the DPI the document is current set to.Promise<Number>
getImageData()Returns the raw image data for the current design.Promise<ImageData>
getShutterstockAssetIds()Returns an array of Shutterstock asset IDs used in the design.Promise<Array>
getThumbnail()Retrieves a blob representing the thumbnail of the first page of the current design.Promise<Blob>
hide()Hides the Editor UI from view.Promise
launch()Initializes the Editor instance.Promise
setCustomData(data)Set the list of available data for a custom namespace. Refer to Custom Templates, Custom Text Templates and Custom Uploads for more examples.Promise
setDesign(json)Sets the current design to the supplied JSON data.Promise
setDPIMode(String)Sets the dpiMode to auto or normal. Any value other than auto will be considered normal.Promise
setEditorUnits(<String>)Set the units for the artboard/design size. Allowed string values are 'PIXELS', 'INCHES' and 'CENTIMETERS'.Promise
setOverlayFromSVG(<String>)Pass an SVG string to set an overlay in the center of the canvas. The overlay is unaffected by user input and does not show up in rendered outputs. The overlay will always appear on top of designs.Promise
show()Make the Editor UI visible.Promise
config.disableFeature(feature)Disable a feature: text, search, pages, etc.Promise
config.enableFeature(feature)Enable a feature: templates, custom_uploads, background_removal, etc.Promise
config.setTheme(themeObject)Configure Shutterstock Editor theme. Refer to the Theming section.Promise
pages.setMaxPages(numberOfPages)Sets the max number of pages a user is allowed to add. The numberOfPages value is an integer.Promise
pages.getIds()Every page of a design is given a unique ID. getIds returns an array of those IDs.Promise<Array>
pages.getCurrent()Return the ID of the current page.Promise<Number>
pages.add()Add a page to the current design.Promise
pages.move(id, toBeforeId)Move page id before the page toBeforeId.Promise
pages.select(id)Using a page's id, make it the active page.Promise
pages.delete(id)Remove a page using its id.Promise
ui.showActivityIndicator()Display a loading interface over the design area.Promise
ui.hideActivityIndicator()Hide the loading interface displayed by ui.showActivityIndicator.Promise
ui.addHeadline()Add a sample text on the board, exactly like the "Add Headline" button in the text panel.Promise
ui.addSubheadline()Add a sample text on the board, exactly like the "Add Subheadline" button in the text panel.Promise
ui.addText()Add a sample text on the board, exactly like the "Add body" text button in the text panel.Promise

Using custom startup dimensions

As described in options, Editor can be started with a custom canvas size. To do so, you will need to set the options's canvas property to an object with a width and height property. Those values will be translated into the canvas's width and height in pixels. You may want to start with a canvas whose size is set in inches, which can include a decimal. In that case, knowing that Editor's default resolution is 300 DPI will help you calculate your desired size. In order to start with a canvas that's 1.8 inches wide and 3.5 inches high, you'll need to multiply by 300 DPI (dots per inch): 1.8 * 300 = 540px and 3.5 * 300 = 1050px. In the case of centimeters, to start with a canvas that's 10cm by 23.5cm, you'll need to multiply by 118.11 (the equivalent of 300 DPI for centimeters): 23.5 * 118.11 = 2775.585px. Then, you'll want to round that value up to 2776.

When working with centimeters, it isn't always possible to find a perfect size. PNG and JPEG images are in pixels and DPI is a round number. Consider that in 1 centimeter there are 118.11 pixels at 300 DPI. Rounding to the nearest pixel, there is a margin of error of around 0.1mm on the final image size at 300 DPI, 0.2mm at 150 DPI, and around 0.4mm at 72 DPI.

Using getBlob for PDFs

🚫 For access to this feature, please contact apisupport@shutterstock.com or your Shutterstock representative

Currently our PDFs are in vector format and do not support gradients, shadows or opacity. If you plan to use this format, we suggest disabling some Editor functionality in order to avoid mixing incompatible options with PDFs:

editor.config.disableFeature('shadow');
editor.config.disableFeature('opacity');

When calling getBlob with format: 'pdf' you can specify the following options: multipage: If true, you will get a PDF containing every page of your design. If false, you will only get the active page, which corresponds to whichever page the user is looking at when getBlob is executed. scaleDownLargePages: If the page is more than 200 inches, it will get scaled down to 1/10 its size. This is to help old PDF software that is locked at PDF 1.3, allowing for a maximum page size of 14400pts. PDF quality won't be affected. By default, this option is set to true.

Regardless of your DPI settings, the generated PDF will always include the highest possible resolution of the image we can provide. By default, PDFs are 72pt per inch and do not have DPI as a format. Your DPI settings won't be reflected in the final file.

Using custom templates

🚫 For access to this feature, please contact apisupport@shutterstock.com or your Shutterstock representative

With the custom_templates feature the integrator has the option to surface a panel containing templates for the end user to start designing from. Aside from containing integrator-provided templates, the panel is the same as the standard templates panel.

In order to be used, custom templates can be enabled with the enableFeature method:

editor.config.enableFeature('custom_templates');

In order to populate the custom templates panels, integrators need to create their designs and store them in a backend for retrieval. Once those templates have been fetched, the panel can be populated with the method setCustomData, which takes an array of objects matching the following schema:

const customTemplates = [{
  id: 'uniqueId', // a uniqueId generated by the integrator for template retrieval
  width: 1000, // width of the template
  height: 3000, // height of the template
  previewUrl: 'https://upload.wikimedia.org/wikipedia/commons/8/81/Wikimedia-logo.svg' // link to a publicly available thumbnail of the template
}, {
  ...
}];

editor.setCustomData({ namespace: 'templates', data: customTemplates });

Now the panel is populated and ready to be opened. When the user clicks on the templates panel icon, a 2-column list of template thumbnails will appear in the order they were provided by the integrator. Depending on their aspect ratio, they will be divided between the left and right columns.

Calling setCustomData with a new array of templates, the integrator can change or reorder the list at will.

Once the user clicks on a template's thumbnail, the objectRequest event will be fired. In order for the feature to work, the integrator needs to set up a listener for this event. A classic example of handling the event is to retrieve a template and fetch it inside Editor:

editor.on('objectRequest', ({ type, id, ...otherData }) => {
  // Do not execute code if objectRequest is of another type
  if (type === 'template') {
    // Show a loading indicator
    editor.ui.showActivityIndicator();
    return myCustomApi.fetchTemplate(id).then((response) => {
      return editor.setDesign(response.templateState).then(() => {
        // Hide the loading indicator
        editor.ui.hideActivityIndicator();
      });
    });
  }
});

templateState is a custom template which has been fetched from some backend. In order to create your custom templates, you can use your own tooling based on the Editor SDK. For instance, we can use the editor.getDesign method to help us in the creation of templates:

Setting up an internal page where an instance of Editor is at the disposal of some creative people, Editor's primaryActionButton can be configured to send the design data to a dedicated API:

// Enable Shutterstock's default templates to get some inspiration
editor.config.enableFeature('templates');
// Set up your primary action to save the template somewhere
editor.on('primaryAction', () => {
  return Promise.all([editor.getDesign(), editor.getThumbnail()]).then(
    ([design, thumbnail]) => {
      const template = {
        state: design,
        thumbnail: thumbnail,
        width: design.state.pages[0].width,
        height: design.state.pages[0].height,
      };
    }
  );
  return myCustomApi.addCustomTemplateToDB(template);
});

An example of a custom templates implementation can be found here.

Custom text-templates

🚫 For access to this feature, please contact apisupport@shutterstock.com or your Shutterstock representative

Custom text-templates are custom templates without images. Text, shapes and emojis can be used to create text-templates for your users to add their designs into. Aside from being loaded differently and displayed in the text panel, text-templates are the same as templates——they even share the same data structure.

When populating the text-template panel with editor.setCustomData, use the namespace of textTemplates:

const customTemplates = [{
  id: 'uniqueId', // a uniqueId generated by the integrator for text-template retrieval
  width: 1000, // width of the text-template
  height: 3000, // height of the text-template
  previewUrl: 'https://upload.wikimedia.org/wikipedia/commons/8/81/Wikimedia-logo.svg' // link to a publicly available thumbnail of the text-template
}, {
  ...
}];

editor.setCustomData({
  namespace: 'textTemplates',
  data: customTemplates,
});

When handling a text-template objectRequest, the type changes from template to textTemplate:

editor.on('objectRequest', ({ type, id, ...otherData }) => {
  // Do not execute code if objectRequest is of another type
  if (type === 'textTemplate') {
    // Show a loading indicator
    editor.ui.showActivityIndicator();
    return myCustomApi.fetchTemplate(id).then((response) => {
      return editor.addGroup(response.templateState).then(() => {
        // Hide the loading indicator
        editor.ui.hideActivityIndicator();
      });
    });
  }
});

Custom uploads

🚫 For access to this feature, please contact apisupport@shutterstock.com or your Shutterstock representative

To use custom uploads, you must have API endpoints that your instance of Editor can use to store and retrieve your uploads. It's up to you to set up authentication and authorization so that each user sees only their own uploads. At minimum, the API must support these commands:

  • Retrieve a list of available images
  • Upload an image
  • Delete an uploaded image

The example instance of Editor simulates this API by storing the templates in an uploads array and using the myBackendApi object to store and retrieve uploads. The uploads array contains information that Editor needs about the image, including its width, height and a link to a thumbnail.

In the example, all users have access to all uploads: you must set up your instance to show the correct uploads to the correct users. In the example, the uploads array is temporary and pre-filled, so when you refresh the example page, the uploads revert to the original state.

The following code shows mocked examples of the three endpoints:

const myBackendApi = {
  fetchUserAssets: () => Promise.resolve(uploads),
  deleteUserAsset: (deleteId) => {
    uploads = uploads.filter(({ id }) => id !== deleteId);
    return Promise.resolve(uploads);
  },
  putUserAsset: ({ tempId, width, height, fileObj }) => {
    const newData = {
      id: tempId,
      width,
      height,
      url: URL.createObjectURL(fileObj),
      previewUrl: URL.createObjectURL(fileObj),
    };
    uploads.unshift(newData);
    return Promise.resolve('ok');
  },
};

To configure your instance of Editor to accept and use uploaded images, you must provide event handler functions for these Editor SDK events:

  • fileUpload: Happens when the user uploads an image.
  • objectRequest: Happens when the user clicks an uploaded image from the panel
  • objectDeleteRequest: Happens when the user tries to delete an uploaded image.

To upload images in Editor, follow these general steps:

  1. Create an instance of Editor as usual. The example uses an Editor object named editorU for template consumer mode. For full information about creating an instance of Editor, see https://developers.shutterstock.com/documentation/editor-integration.
  2. Enable the custom_uploads feature on your instance:
editor.config.enableFeature('custom_uploads');
  1. Disable the existing uploads feature:
editor.config.disableFeature('uploads');
  1. Retrieve the previously uploaded images from your API and add them to the 'My Uploads' tab with the setCustomData method, as in the following example:
const editor = window.Editor({
  ...getDefaultConfig(),
  primaryActionText: 'DOWNLOAD',
  primaryActionBackgroundColor: 'blue',
  theme: 2,
  container: document.getElementById('template-user'),
});

editor.launch().then(() => {
  editor.config.enableFeature('custom_uploads');
  // Uploads are enabled by default in SDK but are not persistent.
  // Disable them to avoid confusion.
  editor.config.disableFeature('uploads');
  // Load images from the API.
  myBackendApi
    .fetchUserAssets()
    .then((data) => editor.setCustomData({ namespace: 'uploads', data }));
});
  1. Implement the handler for the fileUpload event. In this handler, you store the uploaded image with your API and then refresh the 'My Uploads' tab to show the new image:
// Accept an uploaded image, store it, and refresh the tab.
function customUploadHandler({ type, tempId, ...all }) {
  return myBackendApi.putUserAsset({ ...all }).then(() => {
    myBackendApi.fetchUserAssets().then((data) => {
      // After you handle the upload, you must delete
      // the temporary placeholder that Editor created.
      editor.deleteTemporaryUpload(tempId);
      // Update the My Uploads tab.
      editor.setCustomData({ namespace: 'uploads', data });
    });
  });
  return;
}

editor.on('fileUpload', customUploadHandler);
  1. Implement the handler for the objectRequest event to add the image to the canvas:
// Add an uploaded image to the canvas.
// You can add analytics or business logic to this function to
// keep track about how the customers use the images.
function addCustomImageToCanvas({ type, url }) {
  // Run this handler only if the user is adding an uploaded image to the canvas.
  // You can register other handlers to the objectRequest event
  // to let you register multiple handlers to the same event
  // and keep each handler simple.
  if (type !== 'upload') {
    return;
  }
  // Show a loading indicator, add the image to the canvas, and remove the loading indicator.
  editor.ui.showActivityIndicator();
  editor.addImage({ image: url }).then(() => {
    editor.ui.hideActivityIndicator();
  });
}

editor.on('objectRequest', addCustomImageToCanvas);
  1. Implement the handler for the objectDeleteRequest event to delete the uploaded image via the API and refresh the 'My Uploads' tab:
// Delete an uploaded image.
function customUploadDelete({ type, id }) {
  if (type !== 'upload') {
    return;
  }
  // Ask for a confirmation before deleting the image.
  // You can implement a confirmation in any way that makes sense to you.
  if (!window.confirm(`delete item ${id} ?`)) {
    return;
  }
  // If confirmed, delete the asset and update the list of uploads.
  myBackendApi.deleteUserAsset(id).then(() => {
    myBackendApi
      .fetchUserAssets()
      .then((data) => editor.setCustomData({ namespace: 'uploads', data }));
  });
}
editor.on('objectDeleteRequest', customUploadDelete);

Image upload API notes

Images type The Editor SDK accepts JPG and PNG files up to 25MB in size. These limits are not customizable. Editor can be configured to use any kind of web-supported image format, including SVG and WEBP.

Generating thumbnails The thumbnail images should be generated or served on the server. As an alternative, Editor can be configured to generate the thumbnails client-side, but this process can be slow.

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.

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

editor.setOverlayFromSVG(
  '<svg xmlns="http://www.w3.org/2000/svg" width="526" height="233"><rect x="13" y="14" width="500" height="200" rx="50" ry="100" fill="none" stroke="blue" stroke-width="10" /></svg>'
);
// or alternatively
editor.setOverlayFromSVG('http://www.link.to/mySvgFile.svg');

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

The SVG overlay will get embedded in the data returned from getDesign calls and will be restored using setDesign.

You can see a practical example on how to use it here.

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:
const editor = window.Editor({
  apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can only be used for testing purposes
  language: 'en',
  image: 'https://www2.shutterstock.com/blog/wp-content/uploads/sites/5/2015/05/volcano-header-1440x960.jpg',
  onClose: () => {
    | alert('Goodbye');
  },
  onPrimaryAction: () => {
    editor.getBase64({
      format: 'jpg',
      quality: .91
    }).then((base64) => {
      // Handle base64 image data
      editor.hide();
    });
  }
});
  • You can add listeners to the instance after you initialize it:
editor.on('close', () => {
  alert('Goodbye.');
});

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

NameDescriptionSupplied data
closeCalled when the Editor instance is closednone
designChangedCalled when the user changes the designnone
errorCalled when an error occursError data
fileUploadCalled when uploads or drags an image from their computer into EditorImage data
hideCalled when the Editor instance is hiddennone
objectDeleteRequestCalled when the user tries to delete an uploaded image.Image data
objectRequestCalled when the user clicks an uploaded image from the panelImage data
openCalled when the Editor instance is openednone
primaryactionCalled when the primary action button is activatednone
showCalled when the Editor instance is shownnone

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, like 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.

const editor = window.Editor({
  apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can only be used for testing purposes
  primaryActionText: 'Custom Text',
  onClose() {
    editor = null;
  },
  onFileUpload(data) {
    const reader = new FileReader();
    reader.onload = () => {
      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 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.

Using the theming API

🚫 For access to this feature, please contact apisupport@shutterstock.com or your Shutterstock representative

Editor is fully themeable. The application has a dark and light mode as a base setting. Additionally, the developer can choose a different color for distinct parts of the application, including a custom logo.

A live demo can be found here.

You set your own theme with editor.config.setTheme, passing an object with a specific set of keys, described in the example below. Each property is optional and gets added or overrides the current theme. The properties are all css colors and must be solid colors, with no opacity. The only exception is type that supports only two values, light and dark.

editor.config.setTheme({
  headerColor: '#CCCCCC', // background color of the header, the text color will be adjusted automatically
  navButtonColor: '#CCCCCC', // color of the left round buttons, the icon and text color will be adjusted automatically
  toolbarColor: '#CCCCCC', // color of the bar with the tools, under the header. Contrast color for text
  doneButtonColor: '#CCCCCC', // color of all the main buttons that are used for confirm actions
  primaryActionButtonColor: '#CCCCCC', // extra setting for a different color for the top right button on the header, if not specified, doneButtonColor is used
  primaryActionColor: '#CCCCCC', // the top-right button's text color
  accentColor: '#CCCCCC', // color of all controls on the canvas and on the ui, slider, switches, borders, text links.
  type: 'light', // or dark, the general theme of the application. Influences default colors for the categories and color of the canvas and background of tools and panels.
});

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 Shutterstock API.

  • 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, or as privileged, as your Shutterstock API key. Licensing via the Shutterstock API ensures that you, and not your end-users, are in control of licensing.

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

  1. The user clicks on the primary action button (in this case that button might be label "Download" or "License").
  2. The primaryaction event is fired, which triggers its respective callback function. Within that function, we'll call getShutterstockAssetIds to get the IDs of the images included in our design.
  3. Using the Shutterstock API, license the images returned by getShutterstockAssetIds. Your licensing logic should return URLs to your unwatermarked assets.
  4. When calling getBase64, getBlob or getImageData, pass a property called gatekeeperUrls (an array of objects containing the unwatermarked URLs) as the first argument. For more information, see gatekeeperUrls.
editor.on('primaryaction', () => {
  editor.getShutterstockAssetIds().then((imageIds) => {
    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.

Dealing with CORS (Cross Origin Resource Sharing)

In order to import images into the Editor canvas, the images need to be 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 allows 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 });
};

Using the background removal tool

Editor provides a background removal tool that needs licensed images to work properly. What you have to do in order to use the feature changes depending on your flow. If you are pre-licensing the images and loading editor with images without watermarks, the background removal tool will just work normally. Using editor.config.enableFeature('background_removal') is enough to enable the feature.

If you are using Shutterstock images with the watermark you need also to provide a callback for the feature to work. The callback needs to return a Promise. This is a requirement. This callback is the option licenseRequestCallback at initialization time and should look something like this:

const editor = new window.Editor({
  apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can only be used for testing purposes
  licenseRequestCallback: ({ sstkId, size = 'medium' }) => {
    return yourLicensingLogic(sstkId).then((licensedUrl) => {
      return {
        url: 'theResultingGatekeeperUrl',
      };
    });
  },
});

The callback will receive an object as argument containing a sstkId (Shutterstock ID of the image to license) and the size we need to be licensed, that in the case of background removal is the medium size.

Preloading Editor

To improve performance, load a hidden Editor instance on page load or during idle time, then display it when necessary:

// On page load or during idle time
const hiddenEditor = new window.Editor({
  apiKey: 'XDj5YViial3ibnnevAfmGi14',
  hidden: true,
}).launch();

// At some point in the future...
hiddenEditor.show();

Limitations and requirements

Supported browsers:

  • Chrome
  • Firefox
  • Microsoft Edge

The following browsers are deprecated for use with Editor:

  • Internet Explorer 11
  • Microsoft Edge Legacy

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.