Image editor integration

Using the Shutterstock Editor integration, you can let your customers edit images directly on a web page. For a live, editable demo of Editor, see https://codepen.io/sstk-editor/pen/d3dab85c3e8c6eeec2e3226eb0f4eec3.

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 this test key: XDj5YViial3ibnnevAfmGi14. To request an API key so you can host Editor on your own domain, contact us.

Getting started

Follow these steps to embed Shutterstock Editor on a page:

  1. Include the JavaScript library at https://www.shutterstock.com/editor/image/assets/integration_next.js on the page, as in this example:
    <!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>
    This library exposes an Editor property on the global window object.
  2. Create an instance of Editor, as in this example:
    const editor = window.Editor({
      apiKey: 'XDj5YViial3ibnnevAfmGi14',
    });
  3. Launch the instance of Editor with the default configuration with the command editor.launch().

This simple HTML page opens Editor with the testing key:

<!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 be used only for testing purposes
      });
      editor.launch();
    </script>
  </body>
</html>

Configuring Editor

The options object controls the canvas size, logo, language, starting image, how Editor reacts to user actions, and many other things. This example shows some 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 be used only 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 image in the Editor instance by passing a URL or an HTMLImageElement element to the image option.

If you pass a URL, the image must be in PNG or JPEG format and permit cross-origin access from https://shutterstock.com. Properly setting the image's CORS headers enables Editor to load the image anonymously.

If you pass an HTMLImageElement element, Editor inherits the parent page's CORS policy. For example, this code loads an HTMLImageElement element 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 be used only for testing purposes
        image: document.getElementById('image-to-edit'),
      });
      editor.launch();
    </script>
  </body>
</html>

You can also load Shutterstock images by setting the sstkImageId parameter to the ID of the image.

To show a custom logo, set the URL of the logo in the logoUrl parameter. As with images in the image parameter, it must permit cross-origin access from https://shutterstock.com.

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)

You can disable these default features with the command 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, and a height and width, which can be no more than 12,000 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 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 = window.Editor({
  hidden: true,
  apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can be used only for testing purposes
});
editor.launch().then(editor.show);

Setting custom startup dimensions

You can set a starting canvas size for the Editor instance with the canvas option. This option is an object that has height and width fields for the size of Editor in pixels.

If you want to set canvas size in inches, multiply the number of inches by Editor's default resolution of 300 DPI. For example, if you want the canvas to be 1.8 inches wide and 3.5 inches high, set the width to be 1.8 x 300 = 540px and the height to be 3.5 x 300 = 1050px.

To set the canvas size in centimeters, multiply by 118.11, which is 300 DPI in centimeters. For example, to start with a canvas that's 23.5cm in height, set the height field to 23.5 x 118.11 = 2775.585px, rounding up to 2776 pixels.

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.

Options

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

ParameterDescriptionTypeExampleDefault value
apiKeyPartner API key (required)String'89TuQL0KE6SQJ7bcCZHE12Cm'
containerThe HTML element to put the Editor instance inHTML elementdocument.querySelector('#container')'document.querySelector('body')
canvas
canvas.fillInitial fill color of the canvasString'#ff0000'
canvas.heightInitial pixel height of the design (cannot exceed 12000); see Setting custom startup dimensionsInteger2001200
canvas.widthInitial pixel width of the design (cannot exceed 12000); see Setting custom startup dimensionsInteger5001200
debugLog developer debugging informationBooleantruefalse
dpiModeWhen set to 'auto', Editor automatically adjusts the DPI to allow users to work with images with large physical sizesString'auto' or 'normal''normal'
hiddenHide Editor from viewBooleantruefalse
hideCloseButtonHide the 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 language code; see https://www.shutterstock.com for a full list of languages that Shutterstock supportsString'de''en'
licenseRequestCallbackFunction to handle licensing requests from the application (needs to be provided at startup)function({ sstkId, size = 'medium' }) => myLicensingLogic(sstkId)
logoWhether to display a logo at the top left of the pageBooleantruetrue
logoUrlThe URL of a custom logoString'http://example.site/image.jpg'
onFileUploadCallback triggered on file upload; to use this callback, you must set the overrideUploadHandler option to truefunction({ tempId, fileObj, fileName}) => { ... }
onPrimaryActionCallback triggered when the user clicks the primary action buttonfunction() => 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 Editor from closing when the user clicks the close buttonBooleantruefalse
primaryActionTextThe text for the primary action buttonString'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
sstkImageIdAs an alternative to the image parameter, set this field to the ID of a Shutterstock image to load itString'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 parameter can be a URL string or an ImageData object. When properties.resizeArtboard is set to true, the canvas size changes 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)Adds 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 })Adds the corresponding Shutterstock image to the canvas. The id should be passed as a string.Promise<Object> of FabricJS properties for the image
clear()Removes all of the elements on the canvas.Promise
clearHistory()Removes 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 returns 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 returns 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 returns all pages in a design when true. scaleDownLargePages, which is Valid only for PDFs, reduces 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 that the document is currently 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)Sets the available data for a custom namespace. Refer to "Using custom templates", "Using custom text templates" and "Using 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>)Sets 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()Makes the Editor UI visible.Promise
config.disableFeature(feature)Disables a feature: text, search, pages, etc.Promise
config.enableFeature(feature)Enables a feature: templates, custom_uploads, background_removal, etc.Promise
config.setTheme(themeObject)Configures the Shutterstock Editor theme. See the Theming Editor section.Promise
pages.setMaxPages(numberOfPages)Sets the max number of pages that a user is allowed to add. The numberOfPages value is an integer.Promise
pages.getIds()Gets an array of IDs for the pages in the designPromise<Array>
pages.getCurrent()Gets the ID of the current page.Promise<Number>
pages.add()Adds a page to the current design.Promise
pages.move(id, toBeforeId)Moves page id before the page toBeforeId.Promise
pages.select(id)Makes the specified page ID the active page.Promise
pages.delete(id)Removes a page using its id.Promise
ui.showActivityIndicator()Displays a loading interface over the design area.Promise
ui.hideActivityIndicator()Hides the loading interface displayed by ui.showActivityIndicator.Promise
ui.addHeadline()Adds a sample text on the canvas, exactly like the "Add Headline" button in the text panel.Promise
ui.addSubheadline()Adds a sample text on the canvas, exactly like the "Add Subheadline" button in the text panel.Promise
ui.addText()Adds a sample text on the canvas, exactly like the "Add body" text button in the text panel.Promise

Rendering output

In most implementations, the Editor instance renders output when the user clicks the primary action button. You set a callback function as the value of the onPrimaryAction parameter and do whatever you want with the current state of the canvas. To render an image or another file from the canvas, use these functions:

  • download({ format, quality, multipage }): Renders the canvas as an image in the specified format and prompts the user's browser to download the file.
  • getBase64({ quality, format }): Renders the canvas as an image in base 64 format.
  • getBlob({ format, quality, multipage, scaleDownLargePages }): Renders the canvas as a binary large object in the specified format.

In this example, when the user clicks the primary action button, Editor renders the canvas as a JPEG and prompts the user's browser to download the image:

const options = {
  language: 'en',
  apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can be used only for testing purposes
  primaryActionText: 'Download',
  container: document.querySelector('#editor'),
  canvas: {
    width: 500,
    height: 500,
    fill: 'rgb(62, 69, 79)',
  },
  onPrimaryAction: () =>
    editor.ui
      .showActivityIndicator()
      .then(() =>
        // Download rendered image
        editor.download({
          quality: 0.91,
          format: 'jpg',
          multipage: false,
        })
      )
      .then(editor.ui.hideActivityIndicator)
      .then(editor.hide),
};

const editor = window.Editor(options);

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

Licensing Shutterstock images

By default, Editor users can search for Shutterstock images and add them to their designs; no additional code is required. If the canvas contains Shutterstock images, you must handle the licensing calls to the Shutterstock API when the user renders the canvas. You must call the Shutterstock API to handle these licensing calls. For more information about licensing with the Shutterstock API, see Licensing and downloading in the Shutterstock developer platform documentation.

One important reason that you must license images 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.

When the user renders the design, you must use the following workflow to license the assets, store them on your system, and provide the Editor instance with the URLs of the licensed, unwatermarked assets before providing the rendered design to the user:

  1. The user clicks on the primary action button, which in this case might be labeled "Download" or "License."
  2. The primaryaction event is fired, which triggers its respective callback function.
  3. Within that the primary action callback, call the getShutterstockAssetIds() method to get the IDs of the images included in the design.
  4. Using the Shutterstock API, license the images returned by getShutterstockAssetIds and download and store them on your system.
  5. Get the URLs of the licensed, unwatermarked images from your system.
  6. Call getBase64(), getBlob() or getImageData() and set the gatekeeperUrls parameter to an array of objects containing the unwatermarked URLs as the first argument.
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.
      });
  });
});

The gatekeeperUrls parameter is an array of objects with id and url properties, as in this example:

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 at https://codepen.io/sstk-editor/pen/PoYRxWq.

Generating PDFs

Editor can generate PDFs in vector format. These PDFs do not support gradients, shadows, or opacity. For this reason, if you plan to generate PDFs, disable shadows and opacity with this code:

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

To generate PDFs in Editor, your account must be enabled for PDFs. Contact apisupport@shutterstock.com or your Shutterstock representative.

To generate a PDF, call the getBlob() method and set these parameters:

  • Set the format parameter to 'pdf'.
  • To generate PDF with multiple pages, set the multipage parameter to true. If you set it to false, the PDF contains only the page that is currently active on the canvas.
  • To scale large pages down, set the scaleDownLargePages parameter to true (the default). In this case, Editor scales pages larger than 200 inches to 1/10 its size. This setting makes the PDF easier to load on software that is locked at PDF 1.3, which allows a maximum page size of 14400pts. The PDF quality is not affected.
  • Set the quality parameter to a number between 0 and 1 to set the quality of the output.

Regardless of your DPI settings, the generated PDF includes the highest available resolution of the image. 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.

Handling events

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

  • You can add the listeners to the options object, as in this example:

    const editor = window.Editor({
      apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can be used only 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, as in this example:

    editor.on('close', () => {
      alert('Goodbye.');
    });

These ways of adding listeners are equivalent, so you only need to use one method.

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 File -> Upload images in Editor, the fileUpload event is triggered. If a user selects or drags in more than one image at once, each of them triggers 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 file object.

This example reads the file object as a URL and adds an image to the canvas using that URL as a source.

const editor = window.Editor({
  apiKey: 'XDj5YViial3ibnnevAfmGi14', // This key can be used only 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. Then you can add the image to the design with the addImage() method, resulting in a design that can be retrieved later that is not larger than needed. Image resources must respect CORS rules.

Using custom templates

The custom templates feature allows you to set templates of specific sizes that the user can select. These templates appear in the Canvas size menu, as in this picture:

To use custom templates in Editor, your account must be enabled for custom templates. Contact apisupport@shutterstock.com or your Shutterstock representative.

Follow these steps to set custom templates:

  1. Enable the custom templates feature:
    editor.config.enableFeature('custom_templates');
  2. Create or load an array of custom templates. Each template is an object with these fields:
    • id: A unique ID for the template
    • width: The width in pixels
    • height: The height in pixels
    • previewUrl: A URL to a publicly-available thumbnail of the template Here's an example:
      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
      }, {
        ...
      }];
  3. Pass the custom templates to the Editor instance with the setCustomData() method:
    editor.setCustomData({ namespace: 'templates', data: customTemplates });
  4. Set a listener for the objectRequest event, which runs when the user clicks a template, as described below.

Now the custom templates panel appears. When the user clicks on it, it shows a 2-column list of template thumbnails in the order that they are provided in the code. Depending on their aspect ratio, they are divided between the left and right columns.

To change or reorder the templates, you can call the setCustomData() method with a new array of templates.

When the user clicks a template, the objectRequest event is fired with the type parameter set to template. You must set up a listener for this event and either load the template in Editor or do something else. For example, the following code retrieves a template and loads it in the Editor instance. In this example, the templateState field is a custom template that you fetch from your backend:

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();
      });
    });
  }
});

To create custom templates, you can set up an internal instance of Editor and you can use the editor.getDesign() method to save custom templates. For example, this code sets a listener for the primary action button that saves the current canvas as a template on your backend.

// 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);
});

For an example of a custom template implementation, see https://codepen.io/sstk-editor/pen/fa32c4117176def4f693fa8fde030867.

Using custom text templates

Custom text templates are custom templates without images. Text templates include text, shapes and emojis that your users can add their designs to.

To use custom text templates in Editor, your account must be enabled for custom text templates. Contact apisupport@shutterstock.com or your Shutterstock representative.

Setting up text templates is similar to setting up templates. They are loaded differently and displayed in the text panel, but they share the same data structure.

Follow these steps to set custom text templates:

  1. Enable the text templates feature:
    editor.config.enableFeature('custom_text_templates');
  2. Create or load an array of custom templates. Each template is an object with these fields:
    • id: A unique ID for the template
    • width: The width in pixels
    • height: The height in pixels
    • previewUrl: A URL to a publicly-available thumbnail of the template Here's an example:
      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
      }, {
        ...
      }];
  3. Pass the custom text templates to the Editor instance with the setCustomData() method:
    editor.setCustomData({ namespace: 'textTemplates', data: customTemplates });
  4. Set a listener for the objectRequest event, which runs when the user clicks a template, as described below.

The listener must respond to events of the type textTemplate, as in this example:

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();
      });
    });
  }
});

Using custom uploads

Custom uploads are images that you store on your system and provide to the Editor instance.It's up to you to set up an API with authentication and authorization so that each user sees only their own uploads.

To use custom uploads in Editor, your account must be enabled for custom uploads. Contact apisupport@shutterstock.com or your Shutterstock representative.

At minimum, your 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 uploaded images 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 in the panel
  • objectDeleteRequest: Happens when the user deletes an uploaded image

To handle uploaded 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.
  2. Enable the custom_uploads feature:
    editor.config.enableFeature('custom_uploads');
  3. Disable the existing uploads feature:
    editor.config.disableFeature('uploads');
  4. 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 }));
    });
  5. 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);
  6. 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);
  7. 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

When you set up your API to handle custom uploads, take these notes into consideration:

Image 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.

Setting overlays

Editor can set a layer on top of a design that won't be affected by user input. Setting an overlay like this can be useful to indicate the printable area of a design. 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, the width and height must be specified as attributes of the <svg> tag.

Editor positions the SVG at the center of the current canvas.

This example adds a blue oval as an overlay:

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 is embedded in the data returned from getDesign() calls and is restored when you use the setDesign() method.

You can see a practical example on how to use overlays at https://codepen.io/sstk-editor/pen/314e1b01d5f16fb5970936bd633c2993.

Theming Editor

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

To use themes in Editor, your account must be enabled for themes. Contact apisupport@shutterstock.com or your Shutterstock representative.

For an example of an Editor instance with themes, see https://codepen.io/sstk-editor/pen/0c826c3b44720e4e8d61fbf674d5b778.

To set your own theme, pass an object with theme information to the editor.config.setTheme() method. Each property is optional and gets added to or overrides the current theme. The properties are all CSS colors and must be solid colors with no opacity. The only exception is type, which 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.
});

Dealing with CORS (Cross Origin Resource Sharing)

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. 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 works without additional customization. Using editor.config.enableFeature('background_removal') is enough to enable the feature.

If you are using Shutterstock images with watermarks, you must provide a callback for the feature to work. The callback must return a Promise. This callback is the option licenseRequestCallback at initialization time and should look similar to this example:

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

The callback receives an object with the Shutterstock ID of the image to license and the size to license. In the case of background removal, the size is medium.

Preloading Editor

To improve performance, you can load a hidden Editor instance when the page loads or during idle time and then display it when necessary, as in this example:

// 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.