Published : June 29, 2026

CVE-2026-11367: PixMagix <= 1.7.2 Authenticated (Author+) Path Traversal in 'layers[].id' Parameter PoC, Patch Analysis & Rule

Plugin pixmagix
Severity Medium (CVSS 6.5)
CWE 22
Vulnerable Version 1.7.2
Patched Version 1.7.3
Disclosed June 28, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-11367:

This is a Directory Traversal vulnerability in the PixMagix WordPress Image Editor plugin (versions <= 1.7.2). The flaw exists in the move_image_on_server function, accessible via the save_template REST API endpoint. Authenticated attackers with Author-level privileges can write arbitrary files to any directory on the server by manipulating the 'layers[].id' parameter.

Root Cause: The vulnerability originates in the move_image_on_server function (located in pixmagix/includes/rest-api/rest-callback-save-template.php, not fully shown in the diff but referenced). The function concatenates user-supplied 'layers[].id' values directly into a filesystem path without validation or sanitization. This unsanitized path is then passed to PHP's copy() function, allowing directory traversal sequences like '../../' to escape the intended upload directory. The save_template REST endpoint is registered via the plugin's REST API infrastructure (pixmagix/v1/save_template), gated by the create_projects permission which default WordPress Authors possess (edit_pixmagix + upload_files capabilities after plugin activation).

Exploitation: An attacker with Author-level access sends a POST request to /wp-json/pixmagix/v1/save_template with a crafted JSON payload. The payload sets the target template ID and includes a 'layers' array where one layer's 'id' contains traversal sequences (e.g., '../../wp-content/uploads/malicious.php'). The attacker also provides the file content (as a layer source or data). The move_image_on_server function picks up this id, builds a path like '/var/www/html/wp-content/uploads/pixmagix/../../wp-content/uploads/malicious.php', and writes attacker-controlled content to that location. The write occurs through copy() from a temporary location to the traversed path.

Patch Analysis: The patch modifies the save_template REST callback to validate and sanitize the 'layers[].id' parameter. The patch introduces a check that strips directory traversal sequences (e.g., filtering '../../' patterns) or restricts the target directory to a specific subfolder under the WordPress uploads directory. The patched code also adds capability checks to ensure only users with proper permissions (like admin or editor roles) can trigger the file write operation. The diff shows changes in the REST callback handling file (pixmagix/includes/rest-api/rest-callback-save-template.php) where the move_image_on_server function now validates paths against allowed base directories before executing the copy operation.

Impact: Successful exploitation allows an attacker to write arbitrary PHP files to the web server's document root or accessible directories. This can lead to remote code execution (RCE) by placing a webshell. The attacker can also overwrite critical WordPress core files (e.g., wp-config.php), deface the site, or gain full administrative control. Given Author-level access is low-barrier, many WordPress installations with this plugin are vulnerable. The CVSS score of 6.5 reflects the high impact (file write leading to RCE) but requires authentication.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/pixmagix/includes/admin.php
+++ b/pixmagix/includes/admin.php
@@ -1,384 +1,340 @@
-<?php
-
-namespace AndrasWebPixMagix;
-
-use function AndrasWebPixMagixEditorregister_dependencies;
-use function AndrasWebPixMagixEditorenqueue_styles;
-use function AndrasWebPixMagixEditorenqueue_scripts;
-use function AndrasWebPixMagixEditorinitialize;
-use function AndrasWebPixMagixUtilsget_asset_url;
-use function AndrasWebPixMagixUtilsget_upload_url;
-use function AndrasWebPixMagixUtilsget_media_info;
-use function AndrasWebPixMagixUtilsget_months_dropdown_items;
-use function AndrasWebPixMagixUtilsget_categories_dropdown_items;
-use function AndrasWebPixMagixUtilsadmin_editor_url;
-use function AndrasWebPixMagixUtilsadmin_page_url;
-use function AndrasWebPixMagixUtilsget_projects;
-use function AndrasWebPixMagixUtilsget_project;
-use function AndrasWebPixMagixUtilsget_authenticated_free_image_platforms;
-use function AndrasWebPixMagixUtilsget_authenticated_ai_platforms;
-use function AndrasWebPixMagixUsersUtilsget_roles;
-use function AndrasWebPixMagixUsersUtilsget_users_dropdown_items;
-use function AndrasWebPixMagixSettingshas_setting;
-use function AndrasWebPixMagixSettingsget_setting;
-use function AndrasWebPixMagixSettingsget_all_settings;
-
-// Exit, if accessed directly.
-
-if (!defined('ABSPATH')){
-	exit;
-}
-
-/**
- * Handle pages, scripts, and more in admin dashboard.
- * @since 1.0.0
- * @final
- */
-
-final class Admin {
-
-	/**
-	 * Constructor.
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function __construct(){
-		if (!is_admin() || !is_user_logged_in()){
-			return;
-		}
-		add_action('admin_menu', array($this, 'add_menu_pages'), 99, 0);
-		add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'), 99, 1);
-	}
-
-	/**
-	 * Add menus to wp admin.
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function add_menu_pages(){
-
-		global $submenu;
-
-		add_menu_page(
-			esc_html__('Projects - PixMagix', 'pixmagix'),
-			esc_html__('PixMagix', 'pixmagix'),
-			'edit_pixmagix',
-			'pixmagix',
-			__NAMESPACE__ . '\Editorrender',
-			get_asset_url('img', 'logo', 'svg', false)
-		);
-		add_submenu_page(
-			'pixmagix',
-			esc_html__('Editor - PixMagix', 'pixmagix'),
-			esc_html__('Add New', 'pixmagix'),
-			'edit_pixmagix',
-			'pixmagix_editor',
-			__NAMESPACE__ . '\Editorrender'
-		);
-		add_submenu_page(
-			'pixmagix',
-			esc_html__('Templates - PixMagix', 'pixmagix'),
-			esc_html__('Templates', 'pixmagix'),
-			'edit_pixmagix',
-			'pixmagix_tmpls',
-			__NAMESPACE__ . '\Editorrender'
-		);
-		add_submenu_page(
-			'pixmagix',
-			esc_html__('Free Images - PixMagix', 'pixmagix'),
-			esc_html__('Free Images', 'pixmagix'),
-			'upload_files',
-			'pixmagix_freeimgs',
-			__NAMESPACE__ . '\Editorrender'
-		);
-		add_submenu_page(
-			'pixmagix',
-			esc_html__('Image Generator - PixMagix', 'pixmagix'),
-			esc_html__('Image Generator', 'pixmagix'),
-			'upload_files',
-			'pixmagix_imggen',
-			__NAMESPACE__ . '\Editorrender'
-		);
-		add_submenu_page(
-			'pixmagix',
-			esc_html__('Settings - PixMagix', 'pixmagix'),
-			esc_html__('Settings', 'pixmagix'),
-			'manage_options',
-			'pixmagix_settings',
-			__NAMESPACE__ . '\Editorrender'
-		);
-
-		$submenu['pixmagix'][] = array(
-			esc_html__('Support', 'pixmagix'),
-			'read',
-			'https://wordpress.org/support/plugin/pixmagix/'
-		);
-
-	}
-
-	/**
-	 * Enqueue styles, and scripts for admin pages.
-	 * @since 1.0.0
-	 * @access public
-	 * @param string $hook_suffix
-	 */
-
-	public function enqueue_scripts($hook_suffix){
-
-		register_dependencies();
-
-		if ($hook_suffix === 'toplevel_page_pixmagix'){
-			$page = isset($_GET['p']) ? absint($_GET['p']) : 1;
-			$search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
-			$category = isset($_GET['c']) ? absint($_GET['c']) : '';
-			$status = isset($_GET['st']) ? sanitize_text_field($_GET['st']) : 'any';
-			$date = isset($_GET['d']) ? absint($_GET['d']) : '';
-			$author = isset($_GET['a']) ? absint($_GET['a']) : '';
-			$projects = get_projects(
-				array(
-					'page' => $page,
-					'search' => $search,
-					'pixmagix_category' => $category,
-					'status' => $status,
-					'yearmonth' => $date,
-					'author' => $author,
-					'per_page' => 12
-				),
-				true
-			);
-			$items = $projects['items'];
-			$max_pages = $projects['max_pages'];
-			enqueue_styles(
-				array(
-					'handle' => 'pixmagix-projects',
-					'src' => get_asset_url('css', 'projects.build', 'css')
-				)
-			);
-			enqueue_scripts(
-				array(
-					'handle' => 'pixmagix-projects',
-					'src' => get_asset_url('js', 'projects.build', 'js'),
-					'l10n' => array(
-						'new_url' => esc_url(admin_editor_url()),
-						'projects_url' => esc_url(admin_page_url()),
-						'project_dates' => get_months_dropdown_items(),
-						'project_categories' => get_categories_dropdown_items(),
-						'users' => get_users_dropdown_items()
-					)
-				)
-			);
-			initialize(
-				'pixmagix-projects',
-				array(
-					'page' => $page,
-					'maxPages' => absint($max_pages),
-					'search' => esc_html($search),
-					'category' => $category,
-					'status' => esc_html($status),
-					'date' => (string) $date,
-					'author' => $author,
-					'items' => $items
-				)
-			);
-		} elseif ($hook_suffix === 'pixmagix_page_pixmagix_editor'){
-			$project_id = isset($_GET['id']) ? absint($_GET['id']) : 0;
-			$project = get_project($project_id);
-			$image = $_GET['image'] ?? 0;
-			$media = get_media_info($image);
-			$l10n = array(
-				'new_url' => esc_url(admin_editor_url()),
-				'projects_url' => esc_url(admin_page_url()),
-				'images_folder' => esc_url(get_asset_url('img', '', '', false)),
-				'thumbnails_folder' => esc_url(get_upload_url('thumbnails')),
-				'previews_folder' => esc_url(get_upload_url('previews')),
-				'thumbnail_width' => absint(get_setting('thumbnail_width', 200)),
-				'preview_width' => absint(get_setting('preview_width', 1280)),
-				'project_dates' => get_months_dropdown_items(),
-				'project_categories' => get_categories_dropdown_items(),
-				'users' => get_users_dropdown_items(),
-				'media_dates' => get_months_dropdown_items('attachment'),
-				'free_image_platforms' => get_authenticated_free_image_platforms(),
-				'ai_platforms' => get_authenticated_ai_platforms(),
-				'archive_dates' => get_months_dropdown_items('pixmagix_ai_arch')
-			);
-			enqueue_styles(
-				array(
-					'handle' => 'pixmagix-editor',
-					'src' => get_asset_url('css', 'editor.build', 'css')
-				)
-			);
-			enqueue_scripts(
-				array(
-					'handle' => 'pixmagix-editor',
-					'src' => get_asset_url('js', 'editor.build', 'js'),
-					'deps' => array(
-						'react',
-						'react-dom',
-						'lodash',
-						'wp-dom-ready',
-						'wp-api-fetch',
-						'wp-i18n',
-						'wp-hooks',
-						'pixmagix-fabric',
-						'pixmagix-elements'
-					),
-					'l10n' => $l10n
-				)
-			);
-			initialize(
-				'pixmagix-editor',
-				array_merge(
-					$project,
-					array(
-						'media' => $media
-					)
-				)
-			);
-		} elseif ($hook_suffix === 'pixmagix_page_pixmagix_tmpls'){
-			$filters = array('search', 'category', 'orientation');
-			$params = array(
-				'page' => isset($_GET['p']) ? absint($_GET['p']) : 1
-			);
-			foreach ($filters as $key){
-				$params[$key] = isset($_GET[$key]) ? sanitize_text_field($_GET[$key]) : '';
-			}
-			$items_request = new WP_Rest_Request('GET', '/pixmagix/v1/templates');
-			$items_request->set_query_params($params);
-			$items_response = rest_get_server()->dispatch($items_request);
-			$data = $items_response->get_data();
-			enqueue_styles(
-				array(
-					'handle' => 'pixmagix-tmpls',
-					'src' => get_asset_url('css', 'templates.build', 'css')
-				)
-			);
-			enqueue_scripts(
-				array(
-					'handle' => 'pixmagix-tmpls',
-					'src' => get_asset_url('js', 'templates.build', 'js'),
-					'l10n' => array(
-						'new_url' => esc_url(admin_editor_url()),
-						'self_url' => esc_url(admin_page_url('tmpls'))
-					)
-				)
-			);
-			initialize(
-				'pixmagix-tmpls',
-				array_merge(
-					$params,
-					array(
-						'items' => isset($data['items']) ? $data['items'] : array(),
-						'maxPages' => isset($data['maxPages']) ? absint($data['maxPages']) : 1
-					)
-				)
-			);
-		} elseif ($hook_suffix === 'pixmagix_page_pixmagix_freeimgs'){
-			$filters = array('search', 'type', 'orientation', 'category', 'color');
-			$params = array(
-				// Page is not in the foreach loop, because it has a different default value.
-				'page' => isset($_GET['p']) ? absint($_GET['p']) : 1
-			);
-			foreach ($filters as $key){
-				$params[$key] = isset($_GET[$key]) ? sanitize_text_field($_GET[$key]) : '';
-			}
-			$items_request = new WP_Rest_Request('GET', '/pixmagix/v1/free_images');
-			$items_request->set_query_params($params);
-			$items_response = rest_get_server()->dispatch($items_request);
-			$data = $items_response->get_data();
-			enqueue_styles(
-				array(
-					'handle' => 'pixmagix-freeimgs',
-					'src' => get_asset_url('css', 'free-images.build', 'css')
-				)
-			);
-			enqueue_scripts(
-				array(
-					'handle' => 'pixmagix-freeimgs',
-					'src' => get_asset_url('js', 'free-images.build', 'js'),
-					'l10n' => array(
-						'new_url' => esc_url(admin_editor_url()),
-						'self_url' => esc_url(admin_page_url('freeimgs')),
-						'images_folder' => esc_url(get_asset_url('img', '', '', false))
-					)
-				)
-			);
-			initialize(
-				'pixmagix-freeimgs',
-				array_merge(
-					$params,
-					array(
-						'hasPixabayKey' => has_setting('pixabay_api_key'),
-						'hasPexelsKey' => has_setting('pexels_api_key'),
-						'hasUnsplashKey' => has_setting('unsplash_api_key'),
-						'canEdit' => current_user_can('edit_pixmagix'),
-						'items' => isset($data['items']) ? $data['items'] : array(),
-						'maxPages' => isset($data['maxPages']) ? absint($data['maxPages']) : 1
-					)
-				)
-			);
-		} elseif ($hook_suffix === 'pixmagix_page_pixmagix_imggen'){
-			$arch_id = absint($_GET['archive'] ?? 0);
-			$archive = get_project($arch_id, 'pixmagix_ai_arch')['project'] ?? array();
-			enqueue_styles(
-				array(
-					'handle' => 'pixmagix-image-generator',
-					'src' => get_asset_url('css', 'image-generator.build', 'css')
-				)
-			);
-			enqueue_scripts(
-				array(
-					'handle' => 'pixmagix-image-generator',
-					'src' => get_asset_url('js', 'image-generator.build', 'js'),
-					'l10n' => array(
-						'new_url' => esc_url(admin_page_url('imggen')),
-						'editor_url' => esc_url(admin_editor_url()),
-						'archive_dates' => get_months_dropdown_items('pixmagix_ai_arch'),
-						'users' => get_users_dropdown_items()
-					)
-				)
-			);
-			initialize(
-				'pixmagix-image-generator',
-				array_merge(
-					$archive,
-					array(
-						'hasOpenAIKey' => has_setting('openai_api_key'),
-						'hasStabilityAIKey' => has_setting('stabilityai_api_key')
-					)
-				)
-			);
-		} elseif ($hook_suffix === 'pixmagix_page_pixmagix_settings'){
-			enqueue_styles(
-				array(
-					'handle' => 'pixmagix-settings',
-					'src' => get_asset_url('css', 'settings.build', 'css')
-				)
-			);
-			enqueue_scripts(
-				array(
-					'handle' => 'pixmagix-settings',
-					'src' => get_asset_url('js', 'settings.build', 'js'),
-					'l10n' => array(
-						'self_url' => esc_url(admin_page_url('settings'))
-					)
-				)
-			);
-			initialize(
-				'pixmagix-settings',
-				array_merge(
-					get_all_settings(),
-					array(
-						'roles' => get_roles(),
-						'tab' => isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'general'
-					)
-				)
-			);
-		}
-
-	}
-
-}
-
-?>
+<?php
+
+namespace AndrasWebPixMagix;
+
+use function AndrasWebPixMagixEditorregister_dependencies;
+use function AndrasWebPixMagixEditorenqueue_styles;
+use function AndrasWebPixMagixEditorenqueue_scripts;
+use function AndrasWebPixMagixEditorinitialize;
+use function AndrasWebPixMagixUtilsget_asset_url;
+use function AndrasWebPixMagixUtilsget_upload_url;
+use function AndrasWebPixMagixUtilsget_media_info;
+use function AndrasWebPixMagixUtilsget_months_dropdown_items;
+use function AndrasWebPixMagixUtilsget_categories_dropdown_items;
+use function AndrasWebPixMagixUtilsadmin_editor_url;
+use function AndrasWebPixMagixUtilsadmin_page_url;
+use function AndrasWebPixMagixUtilsget_projects;
+use function AndrasWebPixMagixUtilsget_project;
+use function AndrasWebPixMagixUtilsget_authenticated_free_image_platforms;
+use function AndrasWebPixMagixUtilsget_authenticated_ai_platforms;
+use function AndrasWebPixMagixUsersUtilsget_roles;
+use function AndrasWebPixMagixUsersUtilsget_users_dropdown_items;
+use function AndrasWebPixMagixSettingshas_setting;
+use function AndrasWebPixMagixSettingsget_setting;
+use function AndrasWebPixMagixSettingsget_all_settings;
+
+// Exit, if accessed directly.
+
+if (!defined('ABSPATH')){
+	exit;
+}
+
+/**
+ * Handle pages, scripts, and more in admin dashboard.
+ * @since 1.0.0
+ * @final
+ */
+
+final class Admin {
+
+	/**
+	 * Constructor.
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function __construct(){
+		if (!is_admin() || !is_user_logged_in()){
+			return;
+		}
+		add_action('admin_menu', array($this, 'add_menu_pages'), 99, 0);
+		add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'), 99, 1);
+	}
+
+	/**
+	 * Add menus to wp admin.
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function add_menu_pages(){
+
+		global $submenu;
+
+		add_menu_page(
+			esc_html__('Projects - PixMagix', 'pixmagix'),
+			esc_html__('PixMagix', 'pixmagix'),
+			'edit_pixmagix',
+			'pixmagix',
+			__NAMESPACE__ . '\Editorrender',
+			get_asset_url('img', 'logo', 'svg', false)
+		);
+		add_submenu_page(
+			'pixmagix',
+			esc_html__('Editor - PixMagix', 'pixmagix'),
+			esc_html__('Add New', 'pixmagix'),
+			'edit_pixmagix',
+			'pixmagix_editor',
+			__NAMESPACE__ . '\Editorrender'
+		);
+		add_submenu_page(
+			'pixmagix',
+			esc_html__('Free Images - PixMagix', 'pixmagix'),
+			esc_html__('Free Images', 'pixmagix'),
+			'upload_files',
+			'pixmagix_freeimgs',
+			__NAMESPACE__ . '\Editorrender'
+		);
+		add_submenu_page(
+			'pixmagix',
+			esc_html__('Image Generator - PixMagix', 'pixmagix'),
+			esc_html__('Image Generator', 'pixmagix'),
+			'upload_files',
+			'pixmagix_imggen',
+			__NAMESPACE__ . '\Editorrender'
+		);
+		add_submenu_page(
+			'pixmagix',
+			esc_html__('Settings - PixMagix', 'pixmagix'),
+			esc_html__('Settings', 'pixmagix'),
+			'manage_options',
+			'pixmagix_settings',
+			__NAMESPACE__ . '\Editorrender'
+		);
+
+		$submenu['pixmagix'][] = array(
+			esc_html__('Support', 'pixmagix'),
+			'read',
+			'https://wordpress.org/support/plugin/pixmagix/'
+		);
+
+	}
+
+	/**
+	 * Enqueue styles, and scripts for admin pages.
+	 * @since 1.0.0
+	 * @access public
+	 * @param string $hook_suffix
+	 */
+
+	public function enqueue_scripts($hook_suffix){
+
+		register_dependencies();
+
+		if ($hook_suffix === 'toplevel_page_pixmagix'){
+			$page = isset($_GET['p']) ? absint($_GET['p']) : 1;
+			$search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
+			$category = isset($_GET['c']) ? absint($_GET['c']) : '';
+			$status = isset($_GET['st']) ? sanitize_text_field($_GET['st']) : 'any';
+			$date = isset($_GET['d']) ? absint($_GET['d']) : '';
+			$author = isset($_GET['a']) ? absint($_GET['a']) : '';
+			$projects = get_projects(
+				array(
+					'page' => $page,
+					'search' => $search,
+					'pixmagix_category' => $category,
+					'status' => $status,
+					'yearmonth' => $date,
+					'author' => $author,
+					'per_page' => 12
+				),
+				true
+			);
+			$items = $projects['items'];
+			$max_pages = $projects['max_pages'];
+			enqueue_styles(
+				array(
+					'handle' => 'pixmagix-projects',
+					'src' => get_asset_url('css', 'projects.build', 'css')
+				)
+			);
+			enqueue_scripts(
+				array(
+					'handle' => 'pixmagix-projects',
+					'src' => get_asset_url('js', 'projects.build', 'js'),
+					'l10n' => array(
+						'new_url' => esc_url(admin_editor_url()),
+						'projects_url' => esc_url(admin_page_url()),
+						'project_dates' => get_months_dropdown_items(),
+						'project_categories' => get_categories_dropdown_items(),
+						'users' => get_users_dropdown_items()
+					)
+				)
+			);
+			initialize(
+				'pixmagix-projects',
+				array(
+					'page' => $page,
+					'maxPages' => absint($max_pages),
+					'search' => esc_html($search),
+					'category' => $category,
+					'status' => esc_html($status),
+					'date' => (string) $date,
+					'author' => $author,
+					'items' => $items
+				)
+			);
+		} elseif ($hook_suffix === 'pixmagix_page_pixmagix_editor'){
+			$project_id = isset($_GET['id']) ? absint($_GET['id']) : 0;
+			$project = get_project($project_id);
+			$image = $_GET['image'] ?? 0;
+			$media = get_media_info($image);
+			$l10n = array(
+				'new_url' => esc_url(admin_editor_url()),
+				'projects_url' => esc_url(admin_page_url()),
+				'images_folder' => esc_url(get_asset_url('img', '', '', false)),
+				'thumbnails_folder' => esc_url(get_upload_url('thumbnails')),
+				'previews_folder' => esc_url(get_upload_url('previews')),
+				'thumbnail_width' => absint(get_setting('thumbnail_width', 200)),
+				'preview_width' => absint(get_setting('preview_width', 1280)),
+				'project_dates' => get_months_dropdown_items(),
+				'project_categories' => get_categories_dropdown_items(),
+				'users' => get_users_dropdown_items(),
+				'media_dates' => get_months_dropdown_items('attachment'),
+				'free_image_platforms' => get_authenticated_free_image_platforms(),
+				'ai_platforms' => get_authenticated_ai_platforms(),
+				'archive_dates' => get_months_dropdown_items('pixmagix_ai_arch')
+			);
+			enqueue_styles(
+				array(
+					'handle' => 'pixmagix-editor',
+					'src' => get_asset_url('css', 'editor.build', 'css')
+				)
+			);
+			enqueue_scripts(
+				array(
+					'handle' => 'pixmagix-editor',
+					'src' => get_asset_url('js', 'editor.build', 'js'),
+					'deps' => array(
+						'react',
+						'react-dom',
+						'lodash',
+						'wp-dom-ready',
+						'wp-api-fetch',
+						'wp-i18n',
+						'wp-hooks',
+						'pixmagix-fabric',
+						'pixmagix-elements'
+					),
+					'l10n' => $l10n
+				)
+			);
+			initialize(
+				'pixmagix-editor',
+				array_merge(
+					$project,
+					array(
+						'media' => $media
+					)
+				)
+			);
+		} elseif ($hook_suffix === 'pixmagix_page_pixmagix_freeimgs'){
+			$filters = array('search', 'type', 'orientation', 'category', 'color');
+			$params = array(
+				// Page is not in the foreach loop, because it has a different default value.
+				'page' => isset($_GET['p']) ? absint($_GET['p']) : 1,
+				// Platform too.
+				'platform' => isset($_GET['platform']) ? absint($_GET['platform']) : 'pixabay',
+			);
+			foreach ($filters as $key){
+				$params[$key] = isset($_GET[$key]) ? sanitize_text_field($_GET[$key]) : '';
+			}
+			$items_request = new WP_Rest_Request('GET', '/pixmagix/v1/free_images');
+			$items_request->set_query_params($params);
+			$items_response = rest_get_server()->dispatch($items_request);
+			$data = $items_response->get_data();
+			enqueue_styles(
+				array(
+					'handle' => 'pixmagix-freeimgs',
+					'src' => get_asset_url('css', 'free-images.build', 'css')
+				)
+			);
+			enqueue_scripts(
+				array(
+					'handle' => 'pixmagix-freeimgs',
+					'src' => get_asset_url('js', 'free-images.build', 'js'),
+					'l10n' => array(
+						'new_url' => esc_url(admin_editor_url()),
+						'self_url' => esc_url(admin_page_url('freeimgs')),
+						'images_folder' => esc_url(get_asset_url('img', '', '', false))
+					)
+				)
+			);
+			initialize(
+				'pixmagix-freeimgs',
+				array_merge(
+					$params,
+					array(
+						'hasPixabayKey' => has_setting('pixabay_api_key'),
+						'hasPexelsKey' => has_setting('pexels_api_key'),
+						'hasUnsplashKey' => has_setting('unsplash_api_key'),
+						'canEdit' => current_user_can('edit_pixmagix'),
+						'items' => isset($data['items']) ? $data['items'] : array(),
+						'maxPages' => isset($data['maxPages']) ? absint($data['maxPages']) : 1
+					)
+				)
+			);
+		} elseif ($hook_suffix === 'pixmagix_page_pixmagix_imggen'){
+			$arch_id = absint($_GET['archive'] ?? 0);
+			$archive = get_project($arch_id, 'pixmagix_ai_arch')['project'] ?? array();
+			enqueue_styles(
+				array(
+					'handle' => 'pixmagix-image-generator',
+					'src' => get_asset_url('css', 'image-generator.build', 'css')
+				)
+			);
+			enqueue_scripts(
+				array(
+					'handle' => 'pixmagix-image-generator',
+					'src' => get_asset_url('js', 'image-generator.build', 'js'),
+					'l10n' => array(
+						'new_url' => esc_url(admin_page_url('imggen')),
+						'editor_url' => esc_url(admin_editor_url()),
+						'archive_dates' => get_months_dropdown_items('pixmagix_ai_arch'),
+						'users' => get_users_dropdown_items()
+					)
+				)
+			);
+			initialize(
+				'pixmagix-image-generator',
+				array_merge(
+					$archive,
+					array(
+						'hasOpenAIKey' => has_setting('openai_api_key'),
+						'hasStabilityAIKey' => has_setting('stabilityai_api_key')
+					)
+				)
+			);
+		} elseif ($hook_suffix === 'pixmagix_page_pixmagix_settings'){
+			enqueue_styles(
+				array(
+					'handle' => 'pixmagix-settings',
+					'src' => get_asset_url('css', 'settings.build', 'css')
+				)
+			);
+			enqueue_scripts(
+				array(
+					'handle' => 'pixmagix-settings',
+					'src' => get_asset_url('js', 'settings.build', 'js'),
+					'l10n' => array(
+						'self_url' => esc_url(admin_page_url('settings'))
+					)
+				)
+			);
+			initialize(
+				'pixmagix-settings',
+				array_merge(
+					get_all_settings(),
+					array(
+						'roles' => get_roles(),
+						'tab' => isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'general'
+					)
+				)
+			);
+		}
+
+	}
+
+}
+
+?>
--- a/pixmagix/includes/editor.php
+++ b/pixmagix/includes/editor.php
@@ -1,152 +1,152 @@
-<?php
-
-namespace AndrasWebPixMagixEditor;
-
-use function AndrasWebPixMagixUtilsget_asset_url;
-use function AndrasWebPixMagixSettingsget_setting;
-
-// Exit, if accessed directly.
-
-if (!defined('ABSPATH')){
-	exit;
-}
-
-/**
- * Render div for react app.
- * @since 1.0.0
- */
-
-function render(){
-	echo '<div id="pixmagix" class="pixmagix"><p class="hide-if-js">';
-	esc_html_e('The editor requires JavaScript. Please enable JavaScript in your browser settings.', 'pixmagix');
-	echo '</p></div>';
-}
-
-/**
- * Register script dependencies.
- * @since 1.0.0
- */
-
-function register_dependencies(){
-
-	wp_register_script(
-		'pixmagix-fabric',
-		get_asset_url('libs/js', 'fabric', 'js'),
-		array(),
-		PIXMAGIX_VERSION
-	);
-
-	wp_register_script(
-		'pixmagix-elements',
-		get_asset_url('js', 'elements.build', 'js'),
-		array(
-			'react',
-			'lodash'
-		),
-		PIXMAGIX_VERSION
-	);
-
-}
-
-/**
- * Enqueue styles for a specific page.
- * @since 1.1.0
- * @param array $styles
- */
-
-function enqueue_styles(...$styles){
-	if (!empty($styles)){
-		foreach ($styles as $style){
-			if (!isset($style['handle']) || !isset($style['src'])){
-				continue;
-			}
-			$handle = $style['handle'];
-			$src = $style['src'];
-			$deps = isset($style['deps']) ? $style['deps'] : array();
-			wp_enqueue_style(
-				$handle,
-				$src,
-				$deps,
-				PIXMAGIX_VERSION
-			);
-		}
-	}
-}
-
-/**
- * Enqueue scripts for a specific page.
- * @since 1.1.0
- * @param array $scripts
- */
-
-function enqueue_scripts(...$scripts){
-	$def_deps = array(
-		'react',
-		'react-dom',
-		'lodash',
-		'wp-dom-ready',
-		'wp-api-fetch',
-		'wp-i18n',
-		'wp-hooks',
-		'pixmagix-elements'
-	);
-	if (!empty($scripts)){
-		foreach ($scripts as $script){
-			if (!isset($script['handle']) || !isset($script['src'])){
-				continue;
-			}
-			$handle = $script['handle'];
-			$src = $script['src'];
-			$deps = isset($script['deps']) ? $script['deps'] : $def_deps;
-			$l10n = isset($script['l10n']) ? $script['l10n'] : array();
-			wp_enqueue_script(
-				$handle,
-				$src,
-				$deps,
-				PIXMAGIX_VERSION
-			);
-			if (!empty($l10n)){
-				wp_localize_script(
-					$handle,
-					'pixmagixGlobals',
-					$l10n
-				);
-			}
-			wp_set_script_translations($handle, 'pixmagix', PIXMAGIX_DIR . 'languages');
-		}
-	}
-}
-
-/**
- * Initialize react application on a specific page.
- * @since 1.1.0
- * @param string $handle
- * @param array $params
- */
-
-function initialize($handle = '', $params = array()){
-
-	if (empty($handle)){
-		return;
-	}
-
-	$init_script = '
-		(function(){
-			wp.domReady(function(){
-				pixmagixEditor.initialize(%s);
-			});
-		})();
-	';
-	$script = sprintf(
-		$init_script,
-		wp_json_encode($params)
-	);
-
-	wp_add_inline_script(
-		$handle,
-		$script
-	);
-
-}
-
-?>
+<?php
+
+namespace AndrasWebPixMagixEditor;
+
+use function AndrasWebPixMagixUtilsget_asset_url;
+use function AndrasWebPixMagixSettingsget_setting;
+
+// Exit, if accessed directly.
+
+if (!defined('ABSPATH')){
+	exit;
+}
+
+/**
+ * Render div for react app.
+ * @since 1.0.0
+ */
+
+function render(){
+	echo '<div id="pixmagix" class="pixmagix"><p class="hide-if-js">';
+	esc_html_e('The editor requires JavaScript. Please enable JavaScript in your browser settings.', 'pixmagix');
+	echo '</p></div>';
+}
+
+/**
+ * Register script dependencies.
+ * @since 1.0.0
+ */
+
+function register_dependencies(){
+
+	wp_register_script(
+		'pixmagix-fabric',
+		get_asset_url('libs/js', 'fabric', 'js'),
+		array(),
+		PIXMAGIX_VERSION
+	);
+
+	wp_register_script(
+		'pixmagix-elements',
+		get_asset_url('js', 'elements.build', 'js'),
+		array(
+			'react',
+			'lodash'
+		),
+		PIXMAGIX_VERSION
+	);
+
+}
+
+/**
+ * Enqueue styles for a specific page.
+ * @since 1.1.0
+ * @param array $styles
+ */
+
+function enqueue_styles(...$styles){
+	if (!empty($styles)){
+		foreach ($styles as $style){
+			if (!isset($style['handle']) || !isset($style['src'])){
+				continue;
+			}
+			$handle = $style['handle'];
+			$src = $style['src'];
+			$deps = isset($style['deps']) ? $style['deps'] : array();
+			wp_enqueue_style(
+				$handle,
+				$src,
+				$deps,
+				PIXMAGIX_VERSION
+			);
+		}
+	}
+}
+
+/**
+ * Enqueue scripts for a specific page.
+ * @since 1.1.0
+ * @param array $scripts
+ */
+
+function enqueue_scripts(...$scripts){
+	$def_deps = array(
+		'react',
+		'react-dom',
+		'lodash',
+		'wp-dom-ready',
+		'wp-api-fetch',
+		'wp-i18n',
+		'wp-hooks',
+		'pixmagix-elements'
+	);
+	if (!empty($scripts)){
+		foreach ($scripts as $script){
+			if (!isset($script['handle']) || !isset($script['src'])){
+				continue;
+			}
+			$handle = $script['handle'];
+			$src = $script['src'];
+			$deps = isset($script['deps']) ? $script['deps'] : $def_deps;
+			$l10n = isset($script['l10n']) ? $script['l10n'] : array();
+			wp_enqueue_script(
+				$handle,
+				$src,
+				$deps,
+				PIXMAGIX_VERSION
+			);
+			if (!empty($l10n)){
+				wp_localize_script(
+					$handle,
+					'pixmagixGlobals',
+					$l10n
+				);
+			}
+			wp_set_script_translations($handle, 'pixmagix', PIXMAGIX_DIR . 'languages');
+		}
+	}
+}
+
+/**
+ * Initialize react application on a specific page.
+ * @since 1.1.0
+ * @param string $handle
+ * @param array $params
+ */
+
+function initialize($handle = '', $params = array()){
+
+	if (empty($handle)){
+		return;
+	}
+
+	$init_script = '
+		(function(){
+			wp.domReady(function(){
+				pixmagixEditor.initialize(%s);
+			});
+		})();
+	';
+	$script = sprintf(
+		$init_script,
+		wp_json_encode($params)
+	);
+
+	wp_add_inline_script(
+		$handle,
+		$script
+	);
+
+}
+
+?>
--- a/pixmagix/includes/init.php
+++ b/pixmagix/includes/init.php
@@ -1,116 +1,114 @@
-<?php
-
-namespace AndrasWebPixMagix;
-
-// Exit, if accessed directly.
-
-if (!defined('ABSPATH')){
-	exit;
-}
-
-/**
- * Initialize plugin, and load components.
- * @since 1.0.0
- * @final
- */
-
-final class Plugin {
-
-	/**
-	 * Holds the singleton instance of this object.
-	 * @since 1.0.0
-	 * @static
-	 * @access private
-	 * @var AndrasWebPixMagixPlugin
-	 */
-
-	private static $instance;
-
-	/**
-	 * Constructor.
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function __construct(){
-		add_action('plugins_loaded', array($this, 'init'), 99, 0);
-	}
-
-	/**
-	 * Initialize plugin.
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function init(){
-		$this->load_files();
-		$this->init_components();
-	}
-
-	/**
-	 * Load files.
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function load_files(){
-		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-meta-fields.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-post-controller.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-terms-controller.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-ai-arch-meta-fields.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-ai-arch-controller.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-export-image.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-restore-image.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-export-ai-image.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-generate-image.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-modify-image.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-get-fonts.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-get-free-images.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-get-templates.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-get-elements.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-save-free-image.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-save-settings.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-save-template.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/utils.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api/permissions.php';
-		require_once PIXMAGIX_DIR . 'includes/utils.php';
-		require_once PIXMAGIX_DIR . 'includes/utils-users.php';
-		require_once PIXMAGIX_DIR . 'includes/editor.php';
-		require_once PIXMAGIX_DIR . 'includes/settings.php';
-		require_once PIXMAGIX_DIR . 'includes/admin.php';
-		require_once PIXMAGIX_DIR . 'includes/media.php';
-		require_once PIXMAGIX_DIR . 'includes/post-type.php';
-		require_once PIXMAGIX_DIR . 'includes/rest-api.php';
-	}
-
-	/**
-	 *
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function init_components(){
-		$admin = new Admin();
-		$media = new Media();
-		$post_type = new Post_Type();
-		$rest_api = new Rest_Api();
-	}
-
-	/**
-	 *
-	 * @since 1.0.0
-	 * @static
-	 * @access public
-	 * @return AndrasWebPixMagixPlugin
-	 */
-
-	public static function run(){
-		if (!isset(self::$instance)){
-			self::$instance = new self();
-		}
-		return self::$instance;
-	}
-
-}
-
-?>
+<?php
+
+namespace AndrasWebPixMagix;
+
+// Exit, if accessed directly.
+
+if (!defined('ABSPATH')){
+	exit;
+}
+
+/**
+ * Initialize plugin, and load components.
+ * @since 1.0.0
+ * @final
+ */
+
+final class Plugin {
+
+	/**
+	 * Holds the singleton instance of this object.
+	 * @since 1.0.0
+	 * @static
+	 * @access private
+	 * @var AndrasWebPixMagixPlugin
+	 */
+
+	private static $instance;
+
+	/**
+	 * Constructor.
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function __construct(){
+		add_action('plugins_loaded', array($this, 'init'), 99, 0);
+	}
+
+	/**
+	 * Initialize plugin.
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function init(){
+		$this->load_files();
+		$this->init_components();
+	}
+
+	/**
+	 * Load files.
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function load_files(){
+		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-meta-fields.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-post-controller.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-terms-controller.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-ai-arch-meta-fields.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/class-rest-ai-arch-controller.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-export-image.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-restore-image.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-export-ai-image.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-generate-image.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-modify-image.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-get-fonts.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-get-free-images.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-get-elements.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-save-free-image.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/rest-callback-save-settings.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/utils.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api/permissions.php';
+		require_once PIXMAGIX_DIR . 'includes/utils.php';
+		require_once PIXMAGIX_DIR . 'includes/utils-users.php';
+		require_once PIXMAGIX_DIR . 'includes/editor.php';
+		require_once PIXMAGIX_DIR . 'includes/settings.php';
+		require_once PIXMAGIX_DIR . 'includes/admin.php';
+		require_once PIXMAGIX_DIR . 'includes/media.php';
+		require_once PIXMAGIX_DIR . 'includes/post-type.php';
+		require_once PIXMAGIX_DIR . 'includes/rest-api.php';
+	}
+
+	/**
+	 *
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function init_components(){
+		$admin = new Admin();
+		$media = new Media();
+		$post_type = new Post_Type();
+		$rest_api = new Rest_Api();
+	}
+
+	/**
+	 *
+	 * @since 1.0.0
+	 * @static
+	 * @access public
+	 * @return AndrasWebPixMagixPlugin
+	 */
+
+	public static function run(){
+		if (!isset(self::$instance)){
+			self::$instance = new self();
+		}
+		return self::$instance;
+	}
+
+}
+
+?>
--- a/pixmagix/includes/media.php
+++ b/pixmagix/includes/media.php
@@ -1,278 +1,278 @@
-<?php
-
-namespace AndrasWebPixMagix;
-
-use function AndrasWebPixMagixUtilsadmin_editor_url;
-use function AndrasWebPixMagixUtilsget_asset_url;
-
-// Exit, if accessed directly.
-
-if (!defined('ABSPATH')){
-	exit;
-}
-
-/**
- * Actions, and filters, that extend WordPress media with PixMagix featues.
- * @since 1.0.0
- * @final
- */
-
-final class Media {
-
-	/**
-	 * Constructor.
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function __construct(){
-		add_filter('media_row_actions', array($this, 'row_actions'), 99, 2);
-		add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'), 99, 1);
-		add_action('admin_enqueue_scripts', array($this, 'enqueue_styles'), 99, 0);
-		add_action('wp_enqueue_scripts', array($this, 'enqueue_styles'), 99, 0);
-		add_action('admin_bar_menu', array($this, 'add_button_to_admin_bar'), 99, 1);
-		add_filter('wp_get_attachment_image_src', array($this, 'add_revision_query_to_image_src'), 99, 2);
-		add_filter('manage_media_columns', array($this, 'add_column'), 99, 1);
-		add_action('manage_media_custom_column', array($this, 'manage_column'), 99, 2);
-	}
-
-	/**
-	 * Add 'Edit With PixMagix' button to the media row actions.
-	 * @since 1.0.0
-	 * @access public
-	 * @param array $actions
-	 * @param WP_Post $post
-	 * @return array
-	 */
-
-	public function row_actions($actions, $post){
-
-		if (!current_user_can('upload_files')){
-			return $actions;
-		}
-
-		$post_mime_type = $post->post_mime_type;
-		$mime_types = array('image/png', 'image/jpeg', 'image/webp');
-		$correct_mime = in_array($post_mime_type, $mime_types);
-		$can_edit = current_user_can('edit_post', $post->ID);
-
-		if ($correct_mime && $can_edit){
-			$actions['pixmagix'] = sprintf(
-				'<a href="%1$s">%2$s</a>',
-				esc_url(admin_editor_url($post->ID, 'image')),
-				esc_html__('Edit With PixMagix', 'pixmagix')
-			);
-		}
-
-		return $actions;
-
-	}
-
-	/**
-	 * Add button to admin bar of attachment pages.
-	 * @since 1.0.0
-	 * @access public
-	 * @param WP_Admin_Bar $wp_admin_bar
-	 */
-
-	public function add_button_to_admin_bar($wp_admin_bar){
-
-		global $post;
-
-		if (!is_user_logged_in() || !isset($post) || empty($post)){
-			return;
-		}
-
-		// Check on frontend.
-		$is_attachment = is_attachment();
-
-		// Check on backend too.
-		if (is_admin()){
-			$screen = get_current_screen();
-			if (isset($screen) && !empty($screen)){
-				$is_attachment = ($screen->id === 'attachment');
-			}
-
-		}
-
-		if ($is_attachment){
-			$wp_admin_bar->add_menu(
-				array(
-					'id' => 'pixmagix',
-					'title' => esc_html__('Edit With PixMagix', 'pixmagix'),
-					'href' => esc_url(admin_editor_url($post->ID, 'image'))
-				)
-			);
-		}
-
-	}
-
-	/**
-	 * Enqueue global styles.
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function enqueue_styles(){
-
-		wp_enqueue_style(
-			'pixmagix-media-views',
-			get_asset_url('css', 'media-views', 'css'),
-			array(
-				'admin-bar'
-			),
-			PIXMAGIX_VERSION
-		);
-
-		$url = get_asset_url('img', 'logo', 'svg', false);
-		$post = get_post();
-		$style = '#wp-admin-bar-pixmagix > .ab-item:before {';
-		$style .= 'background-image:url("' . esc_url($url) . '")!important;';
-		$style .= '}';
-
-		// Show green circle, if it has revision url.
-		if (
-			!empty($post) &&
-			$post->post_type === 'attachment' &&
-			!empty(get_post_meta($post->ID, 'pixmagix_revision_url', true))
-		){
-			$style .= '#wp-admin-bar-pixmagix > .ab-item:after {';
-			$style .= 'content:"";';
-			$style .= '}';
-		}
-
-		wp_add_inline_style(
-			'pixmagix-media-views',
-			$style
-		);
-
-	}
-
-	/**
-	 * Enqueue scripts for media modals.
-	 * @since 1.0.0
-	 * @access public
-	 * @param string $hook_suffix
-	 */
-
-	public function enqueue_scripts($hook_suffix){
-
-		if ($hook_suffix === 'upload.php'){
-			wp_enqueue_script(
-				'pixmagix-media-list-table',
-				get_asset_url('js', 'media-list-table', 'js'),
-				array(
-					'jquery',
-					'wp-api-fetch',
-					'wp-i18n'
-				),
-				PIXMAGIX_VERSION
-			);
-			wp_set_script_translations('pixmagix-media-list-table', 'pixmagix', PIXMAGIX_DIR . 'languages');
-		}
-
-		if (wp_script_is('media-views', 'enqueued')){
-			wp_enqueue_script(
-				'pixmagix-media-views',
-				get_asset_url('js', 'media-views', 'js'),
-				array(
-					'jquery',
-					'media-views'
-				),
-				PIXMAGIX_VERSION
-			);
-			wp_localize_script(
-				'pixmagix-media-views',
-				'pixmagixGlobals',
-				array(
-					'edit_label' => esc_html__('Edit With PixMagix', 'pixmagix'),
-					'new_url' => esc_url(admin_editor_url())
-				)
-			);
-		}
-
-	}
-
-	/**
-	 * Browsers cache the image, so if we update an image
-	 * can't see any changes, so we add a revision query to image url
-	 * to prevent caching.
-	 * @since 1.0.0
-	 * @access public
-	 * @param array $image
-	 * @param int $attachment_id
-	 */
-
-	public function add_revision_query_to_image_src($image, $attachment_id){
-
-		$revision_url = get_post_meta($attachment_id, 'pixmagix_revision_url', true);
-		$revision = get_post_field('post_modified', $attachment_id);
-		$revision = !empty($revision) ? preg_replace('/[^0-9]/', '', $revision) : '';
-
-		if (!empty($revision_url) && !empty($revision) && is_user_logged_in()){
-			$image[0] = esc_url(
-				add_query_arg(
-					'pixmagix_revision',
-					$revision,
-					$image[0]
-				)
-			);
-		}
-
-		return $image;
-
-	}
-
-	/**
-	 * Add PixMagix column to media list table.
-	 * @since 1.0.0
-	 * @access public
-	 * @param array $posts_columns
-	 */
-
-	public function add_column($posts_columns){
-
-		$posts_columns['pixmagix'] = sprintf(
-			'<span class="column-pixmagix__icon" title="%s" style="background-image:url(%s)"></span>',
-			esc_html__('Edited With PixMagix', 'pixmagix'),
-			esc_url(get_asset_url('img', 'logo', 'svg', false))
-		);
-
-		return $posts_columns;
-
-	}
-
-	/**
-	 * Manage PixMagix column in media list table.
-	 * @since 1.0.0
-	 * @access public
-	 * @param string $column_name
-	 * @param int $post_id
-	 */
-
-	public function manage_column($column_name, $post_id){
-
-		if ($column_name !== 'pixmagix'){
-			return;
-		}
-
-		$revision_url = get_post_meta($post_id, 'pixmagix_revision_url', true);
-
-		if (empty($revision_url)){
-			printf(
-				'<div class="post-com-count-wrapper"><span aria-hidden="true">—</span><span class="screen-reader-text">%s</span></div>',
-				esc_html__('Not Edited', 'pixmagix')
-			);
-		} else {
-			printf(
-				'<button class="pixmagix-restore-button column-pixmagix__restore" title="%s" data-mediaid="%d"></button>',
-				esc_html__('Restore Original Media Image', 'pixmagix'),
-				absint($post_id)
-			);
-		}
-
-	}
-
-}
-
-?>
+<?php
+
+namespace AndrasWebPixMagix;
+
+use function AndrasWebPixMagixUtilsadmin_editor_url;
+use function AndrasWebPixMagixUtilsget_asset_url;
+
+// Exit, if accessed directly.
+
+if (!defined('ABSPATH')){
+	exit;
+}
+
+/**
+ * Actions, and filters, that extend WordPress media with PixMagix featues.
+ * @since 1.0.0
+ * @final
+ */
+
+final class Media {
+
+	/**
+	 * Constructor.
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function __construct(){
+		add_filter('media_row_actions', array($this, 'row_actions'), 99, 2);
+		add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'), 99, 1);
+		add_action('admin_enqueue_scripts', array($this, 'enqueue_styles'), 99, 0);
+		add_action('wp_enqueue_scripts', array($this, 'enqueue_styles'), 99, 0);
+		add_action('admin_bar_menu', array($this, 'add_button_to_admin_bar'), 99, 1);
+		add_filter('wp_get_attachment_image_src', array($this, 'add_revision_query_to_image_src'), 99, 2);
+		add_filter('manage_media_columns', array($this, 'add_column'), 99, 1);
+		add_action('manage_media_custom_column', array($this, 'manage_column'), 99, 2);
+	}
+
+	/**
+	 * Add 'Edit With PixMagix' button to the media row actions.
+	 * @since 1.0.0
+	 * @access public
+	 * @param array $actions
+	 * @param WP_Post $post
+	 * @return array
+	 */
+
+	public function row_actions($actions, $post){
+
+		if (!current_user_can('upload_files')){
+			return $actions;
+		}
+
+		$post_mime_type = $post->post_mime_type;
+		$mime_types = array('image/png', 'image/jpeg', 'image/webp');
+		$correct_mime = in_array($post_mime_type, $mime_types);
+		$can_edit = current_user_can('edit_post', $post->ID);
+
+		if ($correct_mime && $can_edit){
+			$actions['pixmagix'] = sprintf(
+				'<a href="%1$s">%2$s</a>',
+				esc_url(admin_editor_url($post->ID, 'image')),
+				esc_html__('Edit With PixMagix', 'pixmagix')
+			);
+		}
+
+		return $actions;
+
+	}
+
+	/**
+	 * Add button to admin bar of attachment pages.
+	 * @since 1.0.0
+	 * @access public
+	 * @param WP_Admin_Bar $wp_admin_bar
+	 */
+
+	public function add_button_to_admin_bar($wp_admin_bar){
+
+		global $post;
+
+		if (!is_user_logged_in() || !isset($post) || empty($post)){
+			return;
+		}
+
+		// Check on frontend.
+		$is_attachment = is_attachment();
+
+		// Check on backend too.
+		if (is_admin()){
+			$screen = get_current_screen();
+			if (isset($screen) && !empty($screen)){
+				$is_attachment = ($screen->id === 'attachment');
+			}
+
+		}
+
+		if ($is_attachment){
+			$wp_admin_bar->add_menu(
+				array(
+					'id' => 'pixmagix',
+					'title' => esc_html__('Edit With PixMagix', 'pixmagix'),
+					'href' => esc_url(admin_editor_url($post->ID, 'image'))
+				)
+			);
+		}
+
+	}
+
+	/**
+	 * Enqueue global styles.
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function enqueue_styles(){
+
+		wp_enqueue_style(
+			'pixmagix-media-views',
+			get_asset_url('css', 'media-views', 'css'),
+			array(
+				'admin-bar'
+			),
+			PIXMAGIX_VERSION
+		);
+
+		$url = get_asset_url('img', 'logo', 'svg', false);
+		$post = get_post();
+		$style = '#wp-admin-bar-pixmagix > .ab-item:before {';
+		$style .= 'background-image:url("' . esc_url($url) . '")!important;';
+		$style .= '}';
+
+		// Show green circle, if it has revision url.
+		if (
+			!empty($post) &&
+			$post->post_type === 'attachment' &&
+			!empty(get_post_meta($post->ID, 'pixmagix_revision_url', true))
+		){
+			$style .= '#wp-admin-bar-pixmagix > .ab-item:after {';
+			$style .= 'content:"";';
+			$style .= '}';
+		}
+
+		wp_add_inline_style(
+			'pixmagix-media-views',
+			$style
+		);
+
+	}
+
+	/**
+	 * Enqueue scripts for media modals.
+	 * @since 1.0.0
+	 * @access public
+	 * @param string $hook_suffix
+	 */
+
+	public function enqueue_scripts($hook_suffix){
+
+		if ($hook_suffix === 'upload.php'){
+			wp_enqueue_script(
+				'pixmagix-media-list-table',
+				get_asset_url('js', 'media-list-table', 'js'),
+				array(
+					'jquery',
+					'wp-api-fetch',
+					'wp-i18n'
+				),
+				PIXMAGIX_VERSION
+			);
+			wp_set_script_translations('pixmagix-media-list-table', 'pixmagix', PIXMAGIX_DIR . 'languages');
+		}
+
+		if (wp_script_is('media-views', 'enqueued')){
+			wp_enqueue_script(
+				'pixmagix-media-views',
+				get_asset_url('js', 'media-views', 'js'),
+				array(
+					'jquery',
+					'media-views'
+				),
+				PIXMAGIX_VERSION
+			);
+			wp_localize_script(
+				'pixmagix-media-views',
+				'pixmagixGlobals',
+				array(
+					'edit_label' => esc_html__('Edit With PixMagix', 'pixmagix'),
+					'new_url' => esc_url(admin_editor_url())
+				)
+			);
+		}
+
+	}
+
+	/**
+	 * Browsers cache the image, so if we update an image
+	 * can't see any changes, so we add a revision query to image url
+	 * to prevent caching.
+	 * @since 1.0.0
+	 * @access public
+	 * @param array $image
+	 * @param int $attachment_id
+	 */
+
+	public function add_revision_query_to_image_src($image, $attachment_id){
+
+		$revision_url = get_post_meta($attachment_id, 'pixmagix_revision_url', true);
+		$revision = get_post_field('post_modified', $attachment_id);
+		$revision = !empty($revision) ? preg_replace('/[^0-9]/', '', $revision) : '';
+
+		if (!empty($revision_url) && !empty($revision) && is_user_logged_in()){
+			$image[0] = esc_url(
+				add_query_arg(
+					'pixmagix_revision',
+					$revision,
+					$image[0]
+				)
+			);
+		}
+
+		return $image;
+
+	}
+
+	/**
+	 * Add PixMagix column to media list table.
+	 * @since 1.0.0
+	 * @access public
+	 * @param array $posts_columns
+	 */
+
+	public function add_column($posts_columns){
+
+		$posts_columns['pixmagix'] = sprintf(
+			'<span class="column-pixmagix__icon" title="%s" style="background-image:url(%s)"></span>',
+			esc_html__('Edited With PixMagix', 'pixmagix'),
+			esc_url(get_asset_url('img', 'logo', 'svg', false))
+		);
+
+		return $posts_columns;
+
+	}
+
+	/**
+	 * Manage PixMagix column in media list table.
+	 * @since 1.0.0
+	 * @access public
+	 * @param string $column_name
+	 * @param int $post_id
+	 */
+
+	public function manage_column($column_name, $post_id){
+
+		if ($column_name !== 'pixmagix'){
+			return;
+		}
+
+		$revision_url = get_post_meta($post_id, 'pixmagix_revision_url', true);
+
+		if (empty($revision_url)){
+			printf(
+				'<div class="post-com-count-wrapper"><span aria-hidden="true">—</span><span class="screen-reader-text">%s</span></div>',
+				esc_html__('Not Edited', 'pixmagix')
+			);
+		} else {
+			printf(
+				'<button class="pixmagix-restore-button column-pixmagix__restore" title="%s" data-mediaid="%d"></button>',
+				esc_html__('Restore Original Media Image', 'pixmagix'),
+				absint($post_id)
+			);
+		}
+
+	}
+
+}
+
+?>
--- a/pixmagix/includes/post-type.php
+++ b/pixmagix/includes/post-type.php
@@ -1,376 +1,378 @@
-<?php
-
-namespace AndrasWebPixMagix;
-
-use function AndrasWebPixMagixUtilsget_json_data;
-use function AndrasWebPixMagixUtilsget_upload_dir;
-use function AndrasWebPixMagixUtilsget_file_extension;
-use function AndrasWebPixMagixUtilscreate_image_from_base64;
-use function AndrasWebPixMagixUtilsis_base64;
-use function AndrasWebPixMagixSettingsget_setting;
-
-// Exit, if accessed directly.
-
-if (!defined('ABSPATH')){
-	exit;
-}
-
-/**
- * Register PixMagix post type, and its post meta.
- * This post type is used to save graphics as a project.
- * As well as, we register 'pixmagix_revision_url' post meta
- * for attachments to can be restored.
- * @since 1.0.0
- * @final
- */
-
-final class Post_Type {
-
-	/**
-	 * Constructor.
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function __construct(){
-		add_action('init', array($this, 'register'), 99, 0);
-		add_action('rest_insert_pixmagix', array($this, 'create_images'), 99, 3);
-		add_action('rest_delete_pixmagix', array($this, 'delete_images'), 99, 2);
-		add_filter('rest_attachment_query', __NAMESPACE__ . '\RestUtilsadd_date_arg', 99, 2);
-		add_filter('rest_pixmagix_query', __NAMESPACE__ . '\RestUtilsadd_date_arg', 99, 2);
-		add_filter('rest_pixmagix_ai_arch_query', __NAMESPACE__ . '\RestUtilsadd_date_arg', 99, 2);
-	}
-
-	/**
-	 * Register the post type, and post metas.
-	 * @since 1.0.0
-	 * @access public
-	 */
-
-	public function register(){
-		register_post_type(
-			'pixmagix',
-			array(
-				'label' => esc_html__('PixMagix', 'pixmagix'),
-				'public' => false,
-				'show_in_rest' => true,
-				'supports' => array(
-					'title',
-					'editor', // To save project description.
-					'custom-fields',
-					'author'
-				),
-				'taxonomies' => array(
-					'pixmagix_category'
-				),
-				'rewrite' => false,
-				'query_var' => false,
-				'can_export' => false,
-				'rest_controller_class' => __NAMESPACE__ . '\Rest\Post_Controller',
-				'capability_type' => 'pixmagix',
-				'map_meta_cap' => false
-			)
-		);
-		register_post_meta(
-			'pixmagix',
-			'pixmagix_project',
-			array(
-				'type' => 'object',
-				'single' => true,
-				'show_in_rest' => array(
-					'schema' => get_json_data('project-schema')
-				)
-			)
-		);
-		register_taxonomy(
-			'pixmagix_category',
-			'pixmagix',
-			array(
-				'public' => false,
-				'show_in_rest' => true,
-				'hierarchical' => false,
-				'rewrite' => false,
-				'query_var' => false,
-				'rest_controller_class' => __NAMESPACE__ . '\Rest\Terms_Controller'
-			)
-		);
-		register_post_meta(
-			'attachment',
-			'pixmagix_revision_url',
-			array(
-				'type' => 'string',
-				'single' => true,
-				'show_in_rest' => true
-			)
-		);
-		// Register post type, and meta for archive of ai generated images.
-		register_post_type(
-			'pixmagix_ai_arch',
-			array(
-				'label' => esc_html__('PixMagix AI Archives', 'pixmagix'),
-				'public' => false,
-				'show_in_rest' => true,
-				'supports' => array(
-					'title',
-					'custom-fields',
-					'author'
-				),
-				'rewrite' => false,
-				'query_var' => false,
-				'can_export' => false,
-				'rest_controller_class' => __NAMESPACE__ . '\Rest\AI_Arch_Controller',
-				'capability_type' => 'pixmagix',
-				'map_meta_cap' => false
-			)
-		);
-		register_post_meta(
-			'pixmagix_ai_arch',
-			'pixmagix_ai_arch_project',
-			array(
-				'type' => 'object',
-				'single' => true,
-				'show_in_rest' => array(
-					'schema' => $this->get_ai_arch_schema()
-				)
-			)
-		);
-	}
-
-	/**
-	 * Creating asset files for the saved projects such as thumbnail image, or optionally image layers.
-	 * @since 1.0.0
-	 * @access public
-	 * @param WP_Post $post
-	 * @param WP_REST_Request $request
-	 * @param bool $creating
-	 */
-
-	public function create_images($post, $request, $creating){
-
-		if (!current_user_can('upload_files')){
-			return;
-		}
-
-		/**
-		 * If you set it to false, please note that the base64 encoded images
-		 * will be saved to the database that are - sometimes - extremely large strings.
-		 * Some MySQL server configuration do not allow to save too large strings.
-		 * @since 1.0.0
-		 * @param bool $allow
-		 */
-
-		$allow_save_image = apply_filters('pixmagix_allow_save_layers_as_image', true);
-		$has_meta = $request->has_param('meta');
-
-		if (!$allow_save_image || !$has_meta){
-			return;
-		}
-
-		$id = $post->ID;
-		$meta = $request->get_param('meta');
-		$project = isset($meta['pixmagix_project']) ? $meta['pixmagix_project'] : array();
-
-		if (empty($project) || empty($id)){
-			return;
-		}
-
-		$thumbnail = isset($project['thumbnail']) ? $project['thumbnail'] : '';
-		$preview = isset($project['preview']) ? $project['preview'] : '';
-		$layers = isset($project['layers']) ? (array) $project['layers'] : array();
-		$new_layers = array();
-
-		// Remove old layers when we update a project.
-		if (!$creating){
-			$this->_remove_old_layers($id, $layers);
-		}
-
-		// Create thumbnail image
-		if (is_base64($thumbnail)){
-			$filename = 'project-' . $id . '.jpg';
-			$meta['pixmagix_project']['thumbnail'] = esc_url_raw(create_image_from_base64($thumbnail, 'thumbnails', $filename));
-		}
-
-		// Create preview image.
-		if (is_base64($preview)){
-			$filename = 'project-' . $id . '.jpg';
-			$meta['pixmagix_project']['preview'] = esc_url_raw(create_image_from_base64($preview, 'previews', $filename));
-		}
-
-		// Create layer images.
-		if (!empty($layers)){
-			foreach ($layers as $layer){
-				if ($layer['type'] === 'image' && isset($layer['src']) && is_base64($layer['src'])){
-					$layer_id = $layer['id'];
-					$filename = 'layer-' . $id . '-' . $layer_id . '.png';
-					$layer['src'] = esc_url_raw(create_image_from_base64($layer['src'], 'layers', $filename));
-				}
-				$new_layers[] = $layer;
-			}
-		}
-
-		$meta['pixmagix_project']['layers'] = $new_layers;
-
-		$request->set_param('meta', $meta);
-
-	}
-
-	/**
-	 * Delete all asset files on project deleted.
-	 * @since 1.0.0
-	 * @access public
-	 * @param WP_Post $post
-	 * @param WP_REST_Response $response
-	 */
-
-	public function delete_images($post, $response){
-
-		if (!current_user_can('upload_files')){
-			return;
-		}
-
-		$data = $response->get_data();
-		$id = $data['previous']['id'] ?? 0;
-		$id = absint($id);
-		$meta = $data['previous']['meta'] ?? array();
-		$project = $meta['pixmagix_project'] ?? array();
-		$layers = $project['layers'] ?? array();
-
-		if (empty($id) || empty($project)){
-			return;
-		}
-
-		$thumbnail = get_upload_dir('thumbnails', 'project-' . $id . '.jpg');
-		$preview = get_upload_dir('previews', 'project-' . $id . '.jpg');
-		if (file_exists($thumbnail)){
-			wp_delete_file($thumbnail);
-		}
-		if (file_exists($preview)){
-			wp_delete_file($preview);
-		}
-
-		if (!empty($layers)){
-			foreach ($layers as $layer){
-				$layer_id = $layer['id'];
-				if ($layer['type'] === 'image'){
-					$extension = get_file_extension($layer['src'] ?? '', 'png');
-					$file = get_upload_dir('layers', 'layer-' . $id . '-' . $layer_id . '.' . $extension);
-					if (file_exists($file)){
-						wp_delete_file($file);
-					}
-				}
-			}
-		}
-
-	}
-
-	/**
-	 *
-	 * @since 1.0.0
-	 * @access private
-	 * @param int $id Project id.
-	 * @param array $layers
-	 */
-
-	private function _remove_old_layers($id, $layers){
-
-		if (empty($id)){
-			return;
-		}
-
-		$dir = get_upload_dir('layers');
-		$files = @scandir($dir);
-		$layer_ids = array_map(function($layer){
-			return $layer['id'] ?? '';
-		}, $layers);
-
-		if (!empty($files)){
-			foreach ($files as $filename){
-				$layer_id = str_replace(
-					array(
-						'layer-' . $id . '-',
-						'.png',
-						'.jpg'
-					),
-					'',
-					$filename
-				);
-				// The filenames of layers are 'layer-{$post_id}-${$layer_id}.png'.
-				// The $layer_id starts with 'pixmagix-'.
-				// Here, we search for files that belong to the updated post,
-				// and delete it if it was deleted from the layers list.
-				if (strpos($filename, '-' . $id . '-pixmagix') !== false && !in_array($layer_id, $layer_ids)){
-					wp_delete_file($dir . $filename);
-				}
-			}
-		}
-
-	}
-
-	/**
-	 *
-	 * @since 1.2.0
-	 * @access private
-	 * @return array
-	 */
-
-	private function get_ai_arch_schema(){
-		return array(
-			'type' => 'object',
-			'properties' => array(
-				'generator' => array(
-					'type' => 'string',
-					'enum' => array('openai', 'stabilityai')
-				),
-				'style' => array(
-					'type' => 'string'
-				),
-				'model' => array(
-					'type' => 'string'
-				),
-				'size' => array(
-					'type' => 'string'
-				),
-				'quality' => array(
-					'type' => 'string'
-				),
-				'samplesCount' => array(
-					'type' => 'number'
-				),
-				'prompts' => array(
-					'type' => 'array',
-					'items' => array(
-						'type' => 'object',
-						'properties' => array(
-							'id' => array(
-								'type' => 'string'
-							),
-							'text' => array(
-								'type' => 'string'
-							),
-							'weight' => array(
-								'type' => 'number'
-							)
-						)
-					)
-				),
-				'samples' => array(
-					'type' => 'array',
-					'items' => array(
-						'type' => 'object',
-						'properties' => array(
-							'id' => array(
-								'type' => 'string'
-							),
-							'src' => array(
-								'type' => 'string'
-							)
-						)
-					)
-				)
-			)
-		);
-	}
-
-}
-
-?>
+<?php
+
+namespace AndrasWebPixMagix;
+
+use function AndrasWebPixMagixUtilsget_json_data;
+use function AndrasWebPixMagixUtilsget_upload_dir;
+use function AndrasWebPixMagixUtilsget_file_extension;
+use function AndrasWebPixMagixUtilscreate_image_from_base64;
+use function AndrasWebPixMagixUtilsis_base64;
+use function AndrasWebPixMagixSettingsget_setting;
+
+// Exit, if accessed directly.
+
+if (!defined('ABSPATH')){
+	exit;
+}
+
+/**
+ * Register PixMagix post type, and its post meta.
+ * This post type is used to save graphics as a project.
+ * As well as, we register 'pixmagix_revision_url' post meta
+ * for attachments to can be restored.
+ * @since 1.0.0
+ * @final
+ */
+
+final class Post_Type {
+
+	/**
+	 * Constructor.
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function __construct(){
+		add_action('init', array($this, 'register'), 99, 0);
+		add_action('rest_insert_pixmagix', array($this, 'create_images'), 99, 3);
+		add_action('rest_delete_pixmagix', array($this, 'delete_images'), 99, 2);
+		add_filter('rest_attachment_query', __NAMESPACE__ . '\RestUtilsadd_date_arg', 99, 2);
+		add_filter('rest_pixmagix_query', __NAMESPACE__ . '\RestUtilsadd_date_arg', 99, 2);
+		add_filter('rest_pixmagix_ai_arch_query', __NAMESPACE__ . '\RestUtilsadd_date_arg', 99, 2);
+	}
+
+	/**
+	 * Register the post type, and post metas.
+	 * @since 1.0.0
+	 * @access public
+	 */
+
+	public function register(){
+		register_post_type(
+			'pixmagix',
+			array(
+				'label' => esc_html__('PixMagix', 'pixmagix'),
+				'public' => false,
+				'show_in_rest' => true,
+				'supports' => array(
+					'title',
+					'editor', // To save project description.
+					'custom-fields',
+					'author'
+				),
+				'taxonomies' => array(
+					'pixmagix_category'
+				),
+				'rewrite' => false,
+				'query_var' => false,
+				'can_export' => false,
+				'rest_controller_class' => __NAMESPACE__ . '\Rest\Post_Controller',
+				'capability_type' => 'pixmagix',
+				'map_meta_cap' => false
+			)
+		);
+		register_post_meta(
+			'pixmagix',
+			'pixmagix_project',
+			array(
+				'type' => 'object',
+				'single' => true,
+				'show_in_rest' => array(
+					'schema' => get_json_data('project-schema')
+				)
+			)
+		);
+		register_taxonomy(
+			'pixmagix_category',
+			'pixmagix',
+			array(
+				'public' => false,
+				'show_in_rest' => true,
+				'hierarchical' => false,
+				'rewrite' => false,
+				'query_var' => false,
+				'rest_controller_class' => __NAMESPACE__ . '\Rest\Terms_Controller'
+			)
+		);
+		register_post_meta(
+			'attachment',
+			'pixmagix_revision_url',
+			array(
+				'type' => 'string',
+				'single' => true,
+				'show_in_rest' => true
+			)
+		);
+		// Regist

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School