Documentation
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:
- Include the JavaScript library at
https://www.shutterstock.com/editor/image/assets/integration_next.js
on the page, as in this example:This library exposes an<!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>
Editor
property on the globalwindow
object. - Create an instance of Editor, as in this example:
const editor = window.Editor({ apiKey: 'XDj5YViial3ibnnevAfmGi14', });
- 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:
Parameter | Description | Type | Example | Default value |
---|---|---|---|---|
apiKey | Partner API key (required) | String | '89TuQL0KE6SQJ7bcCZHE12Cm' | |
container | The HTML element to put the Editor instance in | HTML element | document.querySelector('#container') | 'document.querySelector('body') |
canvas | ||||
canvas.fill | Initial fill color of the canvas | String | '#ff0000' | |
canvas.height | Initial pixel height of the design (cannot exceed 12000); see Setting custom startup dimensions | Integer | 200 | 1200 |
canvas.width | Initial pixel width of the design (cannot exceed 12000); see Setting custom startup dimensions | Integer | 500 | 1200 |
debug | Log developer debugging information | Boolean | true | false |
dpiMode | When set to 'auto' , Editor automatically adjusts the DPI to allow users to work with images with large physical sizes | String | 'auto' or 'normal' | 'normal' |
hidden | Hide Editor from view | Boolean | true | false |
hideCloseButton | Hide the close button | Boolean | true | false |
image | The URL, image data, or element of an image to add to the canvas | String, ImageData, or HTMLImageElement | 'http://example.site/image.jpg' | |
language | An ISO 639‑1 language code; see https://www.shutterstock.com for a full list of languages that Shutterstock supports | String | 'de' | 'en' |
licenseRequestCallback | Function to handle licensing requests from the application (needs to be provided at startup) | function | ({ sstkId, size = 'medium' }) => myLicensingLogic(sstkId) | |
logo | Whether to display a logo at the top left of the page | Boolean | true | true |
logoUrl | The URL of a custom logo | String | 'http://example.site/image.jpg' | |
onFileUpload | Callback triggered on file upload; to use this callback, you must set the overrideUploadHandler option to true | function | ({ tempId, fileObj, fileName}) => { ... } | |
onPrimaryAction | Callback triggered when the user clicks the primary action button | function | () => editor.ui.showActivityIndicator() | |
overrideUploadHandler | Override the application's default upload handler with the one specified in the fileUpload event or onFileUpload option | Boolean | true | false |
presets | Canvas size presets | Array | [{name: 'Icon', height: 128, width: 128}, {...}] | |
preventClose | Prevent Editor from closing when the user clicks the close button | Boolean | true | false |
primaryActionText | The text for the primary action button | String | 'Download' | 'Download' |
searchLicense | The type of license used by image search (commercial, editorial and/or enhanced) | String or Array | ['commercial', 'editorial', 'enhanced'] | |
showPrimaryActionButton | Show the primary action button | Boolean | true | true |
sstkImageId | As an alternative to the image parameter, set this field to the ID of a Shutterstock image to load it | String | '3434534345' | |
units | Canvas size units | String | 'CENTIMETERS' or 'PIXELS' or 'INCHES' | 'PIXELS' |
Methods
An instance of the Editor library exposes these methods:
Name | Description | Return 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 design | Promise<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:
- The user clicks on the primary action button, which in this case might be labeled "Download" or "License."
- The
primaryaction
event is fired, which triggers its respective callback function. - Within that the primary action callback, call the
getShutterstockAssetIds()
method to get the IDs of the images included in the design. - Using the Shutterstock API, license the images returned by
getShutterstockAssetIds
and download and store them on your system. - Get the URLs of the licensed, unwatermarked images from your system.
- Call
getBase64()
,getBlob()
orgetImageData()
and set thegatekeeperUrls
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:
Name | Description | Supplied data |
---|---|---|
close | Called when the Editor instance is closed | none |
designChanged | Called when the user changes the design | none |
error | Called when an error occurs | Error data |
fileUpload | Called when uploads or drags an image from their computer into Editor | Image data |
hide | Called when the Editor instance is hidden | none |
objectDeleteRequest | Called when the user tries to delete an uploaded image. | Image data |
objectRequest | Called when the user clicks an uploaded image from the panel | Image data |
open | Called when the Editor instance is opened | none |
primaryaction | Called when the primary action button is activated | none |
show | Called when the Editor instance is shown | none |
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:
- Enable the custom templates feature:
editor.config.enableFeature('custom_templates');
- Create or load an array of custom templates.
Each template is an object with these fields:
id
: A unique ID for the templatewidth
: The width in pixelsheight
: The height in pixelspreviewUrl
: 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 }, { ... }];
- Pass the custom templates to the Editor instance with the
setCustomData()
method:editor.setCustomData({ namespace: 'templates', data: customTemplates });
- 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:
- Enable the text templates feature:
editor.config.enableFeature('custom_text_templates');
- Create or load an array of custom templates.
Each template is an object with these fields:
id
: A unique ID for the templatewidth
: The width in pixelsheight
: The height in pixelspreviewUrl
: 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 }, { ... }];
- Pass the custom text templates to the Editor instance with the
setCustomData()
method:editor.setCustomData({ namespace: 'textTemplates', data: customTemplates });
- 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 imageobjectRequest
: Happens when the user clicks an uploaded image in the panelobjectDeleteRequest
: Happens when the user deletes an uploaded image
To handle uploaded images in Editor, follow these general steps:
- Create an instance of Editor as usual. The example uses an Editor object named
editorU
for template consumer mode. - Enable the
custom_uploads
feature:editor.config.enableFeature('custom_uploads');
- Disable the existing uploads feature:
editor.config.disableFeature('uploads');
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 })); });
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);
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);
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 allowswww.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 theaddImage()
oraddBackgroundImage()
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 thecrossorigin
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.