Edit in GitHubLog an issue

Embed SDK Edit Image tutorial

Learn how to implement the Edit Image module using the Adobe Express Embed SDK.

Introduction

Welcome to this hands-on tutorial! We'll walk you through implementing the new Edit Image module of the Adobe Express Embed SDK. By the end, your integration will be able to use all its new V2 features, from the tabbed interface to the significant performance improvements.

What you'll learn

By completing this tutorial, you'll gain practical skills in:

  • Implementing the Edit Image module with the Adobe Express Embed SDK.
  • Applying the various settings and best practices.

What you'll build

You'll build a simple, JavaScript-based web application that allows users to edit images using the Edit Image v2 module of the Adobe Express Embed SDK.

Embed SDK Edit Image experience

Prerequisites

Before you start, make sure you have:

  • An Adobe account (use your existing Adobe ID or create one for free)
  • API credentials from the Adobe Developer Console (Get credentials)
  • Basic knowledge of HTML, CSS, and JavaScript
  • Node.js installed on your development machine (v20.19.0 or higher)
  • A text editor or IDE of your choice

1. Set up the project

1.1 Clone the sample

You can start by cloning the Embed SDK Edit Image sample from GitHub and navigating to the project directory.

Copied to your clipboard
git clone https://github.com/AdobeDocs/embed-sdk-samples.git
cd embed-sdk-samples/code-samples/tutorials/embed-sdk-edit-image

The project will have a structure like this:

Copied to your clipboard
.
├── package.json 📦 Project configuration
├── vite.config.js 🔧 Build configuration
└── src
├── images 📷 Images
│ └── ...
├── index.html 🌐 UI container
├── main.js 💻 Embed SDK logic
└── style.css 🎨 CSS styles

1.2 Set up the API key

Locate the src/.env file and replace the placeholder string in the VITE_API_KEY with your Embed SDK API Key:

Copied to your clipboard
VITE_API_KEY="your-api-key-here!"

1.3 Install dependencies

Install the dependencies by running the following commands:

Copied to your clipboard
npm install
npm run start

The web application will be served at localhost:5555 on a secure HTTPS connection; HTTPS is always required for any Embed SDK integration. Open your browser and navigate to this address to see it in action.

Embed SDK Edit Image integration UI

Click the Edit Image button to launch the Adobe Express Edit Image module with the new tabbed interface, and perform the available actions.

Embed SDK Edit Image remove background

When the users click the Save image button in the top-right corner—this only becomes enabled after the image is edited—the sample project will handle the file transfer between Adobe Express and the web page hosting it, and the edited image will be displayed in lieu of the original.

Embed SDK Edit Image edited image

You can additionally load a different image by clicking the Choose Image button; there is a set of three demo images in the src/images folder, all generated by Adobe Firefly.

2. Load the Edit Image v2 module

You can just read the existing code in the sample, but it's always best to learn by doing! We suggest following along and typing the code in—even small mistakes can lead to important discoveries.

The sample project is a simple web application built with Vite, which takes care of the entire local HTTPS setup and hot reloading.

2.1 Import the Embed SDK

In this tutorial, you'll focus on the JavaScript side of things first—the HTML content is not overly important. Open the project the code editor of your choice. In main.js, remove everything below the Spectrum import statements—you'll rebuild it from scratch.

Copied to your clipboard
// main.js
// Import theme and typography styles from Spectrum Web Components
import "@spectrum-web-components/styles/typography.css";
import "@spectrum-web-components/theme/express/theme-light.js";
import "@spectrum-web-components/theme/express/scale-medium.js";
import "@spectrum-web-components/theme/sp-theme.js";
// Import Spectrum Web Components
import "@spectrum-web-components/button/sp-button.js";
import "@spectrum-web-components/button-group/sp-button-group.js";
import "@spectrum-web-components/divider/sp-divider.js";
import "./style.css";

The imports above allow us to style our web application with Spectrum Web Components and the Adobe Express theme. Let's begin working in main.js by importing the Embed SDK:

Copied to your clipboard
// main.js
//... previous imports ...
// Import the Adobe Express Embed SDK
await import("https://cc-embed.adobe.com/sdk/v4/CCEverywhere.js");
console.log("CCEverywhere loaded", window.CCEverywhere);

2.2 Initialize the Embed SDK

When the Embed SDK is imported, a CCEverywhere object is globally available and must be initialized. There are two sets of parameters that you can pass as option objects:

  • Host Information: containing the API key, Application name, etc.
  • Configuration: optional settings, like locale, delayed sign-in, etc.
Copied to your clipboard
// main.js
//... previous imports ...
// 👀 Required parameters to initialize the Embed SDK
const hostInfo = {
clientId: import.meta.env.VITE_API_KEY,
// The appName must match the Project Name in the Developer Console
appName: "Embed SDK Sample",
};
// Optional parameters
const configParams = { /* ... */ };
// Initialize the Adobe Express Embed SDK
// Destructure the `module` property only
const { module } = await window.CCEverywhere.initialize(
hostInfo,
configParams
);

The hostInfo object is required: the clientId contains your API Key (here, retrieved by Vite from the .env file) and the appName.

2.3 Load the module

The asynchronous CCEverywhere.initialize() method returns an object with three properties. Here, we destructure the module only, because it is the entry point to the editImage() method. In the next section, we'll learn how to use it to launch the Edit Image experience.

Copied to your clipboard
module.editImage({ /* ... */ });

3. Launch the Edit Image experience

3.1 Build the HTML user interface

Before tackling the code needed to run the Edit Image experience, let's have a look at the very simple HTML in our example project.

Copied to your clipboard
<body>
<sp-theme scale="medium" color="light" system="express">
<div class="container">
<header>
<h1>Adobe Express Embed SDK</h1>
<sp-divider size="l"></sp-divider>
<h2>Edit Image Sample</h2>
<p>
The <b>Edit Image</b> button launches
an image editor instance.
</p>
</header>
<main>
<img id="image" src="./images/demo-image-1.jpg"
alt="An Indian woman holding a cat" />
<sp-divider size="l"></sp-divider>
<sp-button-group>
<sp-button id="uploadBtn">Choose Image</sp-button>
<sp-button id="editBtn">Edit Image</sp-button>
</sp-button-group>
<input type="file" id="fileInput" accept="image/*"
style="display: none;" />
</main>
</div>
</sp-theme>
<script type="module" src="./main.js"></script>
</body>

Besides the <sp-theme> wrapper, which styles the entire page with the Adobe Express Spectrum theme, the parts we're interested in are:

  • The <img> element, prepopulated with the (local) image to edit.
  • The <sp-button-group>, which contains the Choose Image and Edit Image buttons.
  • The <input> element, which is hidden and used to upload an alternative image.
  • The <script> tag, which loads the Embed SDK logic.

3.2 Learn the Edit Image method signature

The editImage() method expects four parameters, three of which are optional:

Copied to your clipboard
// module.editImage() function signature
const docConfig = { /* ... */ }; // Image to edit (required)
const appConfig = { /* ... */ }; // Edit Image experience
const exportConfig = { /* ... */ }; // Export options
const containerConfig = { /* ... */ }; // SDK container
module.editImage(docConfig, appConfig, exportConfig, containerConfig);

In this tutorial, we'll focus on the appConfig and docConfig objects, as they are the most relevant for the Edit Image module; you can look at the Full Editor tutorial for more details on the other two parameters.

3.3 Enable the v2 experience in appConfig

First, let's enable the v2 experience by setting the appVersion property to "2" in the appConfig object. Use "1" for the legacy experience, which is the default now but will be deprecated in the future.

Copied to your clipboard
// main.js
//... previous code ...
const appConfig = {
appVersion: "2",
// ...
};

3.4 Familiarize with the docConfig object

The docConfig object, that implements the EditImageDocConfig interface, is used to pass:

  1. The image to edit.
  2. The intent to perform on the image; that is, the preselected action to perform on the image, among the available options.
Copied to your clipboard
interface EditImageDocConfig {
asset?: Asset;
intent?: EditImageIntent;
}

3.5 Build the Asset object

The docConfig.asset property needs to be an object of type Asset—this, as you'd expect, is the image to edit.

There are three kinds of assets:

Regardless of the kind of asset, they share the following interface:

Copied to your clipboard
interface Asset {
name?: string;
type: "image";
dataType: "url" | "base64" | "blob";
data: string | Blob;
}

3.5.1 Build a URL type Asset

To pass an image from a URL, you need to build a UrlAsset object, like this:

Copied to your clipboard
const docConfig = {
asset: {
name: "Demo Image",
type: "image",
dataType: "url",
data: "https://ucf44fba496cfec9066caed2...", // Your presigned URL
},
};

In this tutorial, you can make the local URL work—with a caveat.

Copied to your clipboard
// main.js
//... previous code ...
const expressImage = document.getElementById("image");
const docConfig = {
asset: {
name: "Demo Image",
type: "image",
dataType: "url",
data: expressImage.src // 👈 the <img> element's src attribute
},
};
const appConfig = { appVersion: "2" };
const exportConfig = [ /* ... */ ];
module.editImage(docConfig, appConfig, exportConfig);

As is, this code would log the following error.

CORS error

This is because when passing an image by URL, the Edit Image module (served from https://quick-actions.express.adobe.com) needs to fetch that file from your development server (https://localhost:5555). Because the two origins differ, the browser blocks the request unless your server explicitly says, "other sites may read this." This safeguard prevents a malicious site from poking around your network.

During local development you can loosen the restriction by adding CORS headers in vite.config.js, either by setting the cors.origin property to that specific Adobe Express URL, or to * to allow all origins.

Copied to your clipboard
import { defineConfig } from "vite";
import mkcert from "vite-plugin-mkcert";
export default defineConfig({
root: "src",
server: {
https: true,
port: 5555,
cors: {
// 👇 origin is needed if you want to use asset of type "url"
origin: "https://quick-actions.express.adobe.com", // 👈 add this
credentials: true,
},
},
build: {
outDir: "../dist",
},
plugins: [mkcert()],
});

3.5.2 Build a Blob type Asset

The other common way to edit an image is by passing a BlobAsset object. In our tutorial, we'll cache the default image as a blob fetching the local resource with the cacheDefaultImageBlob() helper function, and using it as the asset's data property.

Copied to your clipboard
// main.js
//... previous code ...
let currentImageBlob = null; // 👈 will hold the blob content
// Cache the default image as a blob
async function cacheDefaultImageBlob() {
const response = await fetch(expressImage.src);
currentImageBlob = await response.blob();
}
await cacheDefaultImageBlob();
const docConfig = {
asset: {
name: "Demo Image",
type: "image",
dataType: "blob",
data: currentImageBlob // 👈 the blob content
},
};
const appConfig = { appVersion: "2" };
const exportConfig = [ /* ... */ ];
module.editImage(docConfig, appConfig, exportConfig);

3.5.3 Build a Base64 type Asset

Although less common, you can also pass an image as a Base64 encoded string. Here's a sample helper function to convert a Blob to Base64 using a FileReader.

Copied to your clipboard
async function imageBlobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result); // returns full data URL
};
reader.onerror = reject;
reader.readAsDataURL(blob); // encodes blob to base64 as a data URL
});
}

3.6 Pass the intent to perform on the image

The docConfig.intent property is used to pass the intent to perform on the image—that is to say, the preselected action to perform on the image, among the available options. When it makes sense, the action can be automatically triggered when the experience is launched, for instance in case of Remove Background.

Copied to your clipboard
const docConfig = {
asset: { /* ... */ },
intent: "crop-image", // 👈 the intent to perform on the image
};

The intent property is an object of type EditImageIntent, which is a subset of the EditFurtherIntent enumeration, and includes only the following options—some of which are Premium features and will consume Generative Credits:

Copied to your clipboard
type EditImageIntent = "add-effects" |
"remove-background" |
"resize-image" |
"crop-image" |
"apply-adjustment" |
"gen-fill" |
"remove-object" |
"insert-object" |
"no-intent";

In this screenshot, the intent was set to "remove-background", which triggered the Remove Background feature as soon as the Edit Image experience was launched.

Edit Image Intent

4. Integrate the Edit Image module

Now that we have all the pieces in place, let's integrate the Edit Image module in the UI. Our simple project needs to implement these features:

  • Pass the default (prepopulated) image to the Edit Image module.
  • Allow the user to select an alternative image, and display it in lieu of the default one.
  • Render the edited image in the UI, and make it available for further editing.

4.1 Edit the default image

This has already been taken care of in the main.js file, where we set the expressImage variable to the <img> element, and cached it as a blob. Now, on the Edit Button click, we can call the editImage() method with a docConfig object where the Asset is a Blob, as we've seen in Build a Blob type Asset.

Copied to your clipboard
// main.js
//... previous code ...
const expressImage = document.getElementById("image");
let currentImageBlob = null;
// Cache the default image as a blob
async function cacheDefaultImageBlob() {
const response = await fetch(expressImage.src);
currentImageBlob = await response.blob();
}
// Get the blob
await cacheDefaultImageBlob();
const appConfig = { appVersion: "2" };
const exportConfig = [ /* ... */ ];
// Edit Button click handler
document.getElementById("editBtn").onclick = async () => {
const docConfig = {
asset: {
type: "image",
name: "Demo Image",
dataType: "blob",
data: currentImageBlob, // Use cached blob
},
// intent: "crop-image", // specify the intent if you want
};
// Launch Adobe Express editor with the current image
module.editImage(docConfig, appConfig, exportConfig);
};

4.2 Select and Edit an alternative image

In the HTML file, we have a hidden <input> element that allows the user to select an alternative image. We can link it to the Choose Image button, so that when the user clicks the button, the File Picker is triggered.

Embed SDK Edit Image choose image

When a new image has been selected, we must do two things:

  1. Update the <img> element's src attribute to display the new image. This is taken care by the onchange event handler of the <input> element, via FileReader.readAsDataURL(). When the file is read, the onload event is triggered, and the result property contains the Base64 encoded string.
  2. Update the currentImageBlob Blob cache, so that it can be passed to the editImage() method.
Copied to your clipboard
// main.js
//... previous code ...
// Click handler for the Choose Image button
document.getElementById("uploadBtn").onclick = () => {
// Trigger the File Picker!
document.getElementById("fileInput").click();
};
// Handle file selection
document.getElementById("fileInput").onchange = (event) => {
const file = event.target.files[0];
if (file && file.type.startsWith("image/")) {
// Dual data flow: cache the File (which is a Blob) for SDK,
// and convert to data URL for display
currentImageBlob = file; // File objects are Blobs, perfect for SDK usage
// Convert to data URL for display in the <img> element
const reader = new FileReader();
reader.onload = (e) => {
expressImage.src = e.target.result; // 👈 Base64 encoded image data
};
reader.readAsDataURL(file);
}
};
// Reuse the Edit Button click handler defined earlier.
// It'll use the refreshed Blob cache if the user has selected a new image
document.getElementById("editBtn").onclick = async () => { /* ... */ };

4.3 Display the edited image

When the user clicks the Save image button in the Edit Image experience, the edited image must be displayed in the <img> element. First of all, the buttons are defined in the exportConfig object, as follows.

Copied to your clipboard
// main.js
//... previous code ...
const exportConfig = [
{
id: "download", label: "Download",
action: { target: "download" }, style: { uiType: "button" },
},
{
id: "save-modified-asset", label: "Save image",
action: { target: "publish" }, style: { uiType: "button" },
},
];

The save-modified-asset button is the one that will be displayed in the Edit Image experience, and it will trigger the onPublish callback, which is defined in the appConfig.callbacks object. It receives the intent and publishParams objects, which contain the intent and the edited image, respectively. In the callback, we:

  • Update the displayed image with the edited result, passing the publishParams.asset[0].data to the expressImage.src property.
  • Update the cached blob with the edited image (for future edits).
Copied to your clipboard
// main.js
//... previous code ...
const appConfig = {
appVersion: "2",
// Callbacks to be used when creating or editing a document
callbacks: {
onCancel: () => {},
onPublish: async (intent, publishParams) => {
console.log("intent", intent);
console.log("publishParams", publishParams);
// Update the displayed image with the edited result
expressImage.src = publishParams.asset[0].data;
// Update our cached blob with the edited image (for future edits)
const response = await fetch(publishParams.asset[0].data);
currentImageBlob = await response.blob();
},
onError: (err) => {
console.error("Error!", err.toString());
},
},
};

This completes the integration of the Edit Image module in the UI, check the complete working example below.

Troubleshooting

Common issues

IssueSolution
Error: "Adobe Express is not available"
Check to have entered the correct API Key in the src/.env file as described here.
Error: "File Type not supported"
Make sure you've set the cors.origin property in vite.config.js to the Adobe Express URL.

Complete working example

Copied to your clipboard
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Embed SDK Sample</title>
</head>
<body>
<sp-theme scale="medium" color="light" system="express">
<div class="container">
<header>
<h1>Adobe Express Embed SDK</h1>
<sp-divider size="l"></sp-divider>
<h2>Edit Image Sample</h2>
<p>
The <b>Edit Image</b> button launches an image editor instance.
</p>
</header>
<main>
<img id="image" src="./images/demo-image-1.jpg" alt="An Indian woman holding a cat" />
<sp-button-group>
<sp-button id="uploadBtn">Choose Image</sp-button>
<sp-button id="editBtn">Edit Image</sp-button>
</sp-button-group>
<input type="file" id="fileInput" accept="image/*" style="display: none;" />
</main>
</div>
</sp-theme>
<script type="module" src="./main.js"></script>
</body>
</html>

Next steps

Congratulations, you've completed the Edit Image tutorial! Edit Image can be tethered from other modules, such as the Generate Image v2, to create a more complex experience, feel free to explore that as well

Need help?

Have questions or running into issues? Join our Community Forum to get help and connect with other developers working with the Adobe Express Embed SDK.

  • Privacy
  • Terms of Use
  • Do not sell or share my personal information
  • AdChoices
Copyright © 2025 Adobe. All rights reserved.