xiaohongshu
xiaohongshu
magento2-widget-creation
Comprehensive guide for creating custom widget modules in Magento 2 that can be inserted into CMS pages and blocks. Covers module structure, widget configuration, templates, JavaScript, CSS, and form submission handling for non-Hyvä themes.
Full skill instructions
This skill provides comprehensive guidance on creating custom widget modules in Magento 2 (standard Luma/Blank themes, not Hyvä-based) that can be inserted into CMS pages, CMS blocks, or any content area using the widget system.
Use this skill when you need to:
Do NOT use this skill for:
A complete widget module requires these components:
app/code/Vendor/ModuleName/
├── registration.php # Module registration
├── etc/
│ ├── module.xml # Module configuration
│ ├── widget.xml # Widget definition
│ ├── email_templates.xml # (Optional) Email templates
│ └── frontend/
│ └── routes.xml # (Optional) For form submissions
├── Block/
│ └── Widget/
│ └── WidgetName.php # Widget block class
├── Controller/ # (Optional) For form handlers
│ └── Index/
│ └── Submit.php
└── view/frontend/
├── templates/
│ └── widget/
│ └── template.phtml # Widget template
├── layout/
│ └── default.xml # (Optional) Load CSS/JS globally
├── web/
│ ├── js/
│ │ └── widget-script.js # (Optional) Custom JS
│ └── css/
│ └── widget-style.css # (Optional) Custom CSS
├── requirejs-config.js # (Optional) JS module mapping
└── email/ # (Optional) Email templates
└── template.html
File: registration.php
<?php
/**
* Copyright © Vendor. All rights reserved.
*/
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Vendor_ModuleName',
__DIR__
);
File: etc/module.xml
<?xml version="1.0"?>
<!--
/**
* Copyright © Vendor. All rights reserved.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Vendor_ModuleName" setup_version="1.0.0">
<sequence>
<module name="Magento_Cms"/>
<module name="Magento_Widget"/>
</sequence>
</module>
</config>
Key Points:
setup_version is legacy but still commonly used<sequence> - Magento_Cms and Magento_Widget are required for widgetsMagento_Email, Magento_Catalog)File: etc/widget.xml
This is where you define your widget's metadata and configurable parameters.
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Vendor. All rights reserved.
*/
-->
<widgets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/widget.xsd">
<widget id="widget_unique_id" class="Vendor\ModuleName\Block\Widget\WidgetName">
<label translate="true">Widget Display Name</label>
<description translate="true">Brief description of what the widget does</description>
<parameters>
<!-- Text Parameter -->
<parameter name="text_param" xsi:type="text" required="false" visible="true">
<label translate="true">Text Label</label>
<description translate="true">Description shown in admin</description>
</parameter>
<!-- Select/Dropdown Parameter -->
<parameter name="select_param" xsi:type="select" required="false" visible="true">
<label translate="true">Select Option</label>
<options>
<option name="option1" value="value1">
<label>Option 1</label>
</option>
<option name="option2" value="value2">
<label>Option 2</label>
</option>
</options>
</parameter>
<!-- Yes/No Parameter -->
<parameter name="enabled" xsi:type="select" required="false" visible="true"
source_model="Magento\Config\Model\Config\Source\Yesno">
<label translate="true">Enable Feature</label>
</parameter>
<!-- CMS Block Chooser -->
<parameter name="block_id" xsi:type="block" visible="true" required="false">
<label translate="true">CMS Block</label>
<block class="Magento\Cms\Block\Adminhtml\Block\Widget\Chooser">
<data>
<item name="button" xsi:type="array">
<item name="open" xsi:type="string">Select Block...</item>
</item>
</data>
</block>
</parameter>
<!-- Category Chooser -->
<parameter name="category_id" xsi:type="select" visible="true"
source_model="Magento\Catalog\Model\Category\Attribute\Source\Categories">
<label translate="true">Category</label>
</parameter>
</parameters>
</widget>
</widgets>
Widget Parameter Types:
text - Simple text inputselect - Dropdown selectionmultiselect - Multiple selectionsblock - CMS block pickerpage - CMS page pickerconditions - Product/category conditions (advanced)Important Attributes:
id - Unique identifier for the widgetclass - Full namespaced path to your block classrequired - Whether parameter is mandatoryvisible - Whether parameter shows in adminFile: Block/Widget/WidgetName.php
The block class handles the widget's logic and data.
<?php
/**
* Copyright © Vendor. All rights reserved.
*/
declare(strict_types=1);
namespace Vendor\ModuleName\Block\Widget;
use Magento\Framework\View\Element\Template;
use Magento\Widget\Block\BlockInterface;
use Magento\Framework\View\Element\Template\Context;
class WidgetName extends Template implements BlockInterface
{
/**
* Template path relative to view/frontend/templates/
*
* @var string
*/
protected $_template = 'Vendor_ModuleName::widget/template.phtml';
/**
* @param Context $context
* @param array $data
*/
public function __construct(
Context $context,
array $data = []
) {
parent::__construct($context, $data);
}
/**
* Get widget parameter with default value
*
* @return string
*/
public function getParameterValue(): string
{
return $this->getData('text_param') ?: 'default value';
}
/**
* Get widget select parameter
*
* @return string
*/
public function getSelectValue(): string
{
return $this->getData('select_param') ?: 'value1';
}
/**
* Check if feature is enabled
*
* @return bool
*/
public function isEnabled(): bool
{
return (bool)$this->getData('enabled');
}
/**
* Get URL for AJAX or form submission
*
* @return string
*/
public function getActionUrl(): string
{
return $this->getUrl('modulename/index/submit');
}
}
Best Practices:
declare(strict_types=1);BlockInterface$this->getData('param_name') to access widget parameters?: operatorFile: view/frontend/templates/widget/template.phtml
Templates render the HTML output. Always escape data for security.
<?php
/**
* Copyright © Vendor. All rights reserved.
*
* @var $block Vendor\ModuleName\Block\Widget\WidgetName
* @var $escaper Magento\Framework\Escaper
*/
$paramValue = $block->getParameterValue();
$selectValue = $block->getSelectValue();
$isEnabled = $block->isEnabled();
$uniqueId = uniqid('widget_');
?>
<?php if ($isEnabled): ?>
<div class="custom-widget" id="<?= $escaper->escapeHtmlAttr($uniqueId) ?>">
<div class="widget-content">
<h3><?= $escaper->escapeHtml(__('Widget Title')) ?></h3>
<p><?= $escaper->escapeHtml($paramValue) ?></p>
<?php if ($selectValue === 'value1'): ?>
<div class="option-one-content">
<!-- Content for option 1 -->
</div>
<?php endif; ?>
<button type="button"
class="action primary widget-button"
data-mage-init='{"widgetName": {"elementId": "<?= $escaper->escapeJs($uniqueId) ?>"}}'>
<?= $escaper->escapeHtml(__('Click Me')) ?>
</button>
</div>
</div>
<?php endif; ?>
Template Best Practices:
$escaper->escapeHtml() for text content$escaper->escapeHtmlAttr() for HTML attributes$escaper->escapeJs() for JavaScript strings$escaper->escapeUrl() for URLs__() for translatable stringsuniqid())@var comments for IDE supportCommon Escaping Methods:
// Text content
<?= $escaper->escapeHtml($text) ?>
// HTML attributes
<div class="<?= $escaper->escapeHtmlAttr($class) ?>">
// JavaScript strings
data-value="<?= $escaper->escapeJs($value) ?>"
// URLs
<a href="<?= $escaper->escapeUrl($url) ?>">
// CSS
<div style="<?= $escaper->escapeCss($style) ?>">
If your widget needs interactivity, add JavaScript using Magento's widget pattern.
File: view/frontend/requirejs-config.js
/**
* Copyright © Vendor. All rights reserved.
*/
var config = {
map: {
'*': {
widgetName: 'Vendor_ModuleName/js/widget-script'
}
}
};
File: view/frontend/web/js/widget-script.js
/**
* Copyright © Vendor. All rights reserved.
*/
define([
'jquery',
'jquery-ui-modules/widget'
], function ($) {
'use strict';
/**
* Widget initialization pattern
*/
$.widget('vendor.widgetName', {
options: {
elementId: '',
ajaxUrl: ''
},
/**
* Widget creation
* @private
*/
_create: function () {
this._bind();
},
/**
* Bind event handlers
* @private
*/
_bind: function () {
var self = this;
this.element.on('click', function (e) {
e.preventDefault();
self._handleClick();
});
},
/**
* Handle click event
* @private
*/
_handleClick: function () {
console.log('Widget clicked!');
console.log('Element ID:', this.options.elementId);
// Example AJAX call
if (this.options.ajaxUrl) {
this._makeAjaxRequest();
}
},
/**
* Make AJAX request
* @private
*/
_makeAjaxRequest: function () {
var self = this;
$.ajax({
url: this.options.ajaxUrl,
type: 'POST',
dataType: 'json',
data: {
// Your data here
},
success: function (response) {
self._handleResponse(response);
},
error: function () {
console.error('Request failed');
}
});
},
/**
* Handle AJAX response
* @private
*/
_handleResponse: function (response) {
if (response.success) {
console.log('Success:', response.message);
} else {
console.error('Error:', response.message);
}
}
});
return $.vendor.widgetName;
});
Initialization in Template:
<button type="button"
data-mage-init='{"widgetName": {
"elementId": "<?= $escaper->escapeJs($uniqueId) ?>",
"ajaxUrl": "<?= $escaper->escapeUrl($block->getActionUrl()) ?>"
}}'>
Click Me
</button>
Alternative Initialization with data-bind (Knockout.js):
<!-- For Knockout.js integration -->
<div data-bind="scope: 'widget-scope'">
<!-- content -->
</div>
<script type="text/x-magento-init">
{
"[data-bind]": {
"Magento_Ui/js/core/app": {
"components": {
"widget-scope": {
"component": "Vendor_ModuleName/js/widget-component"
}
}
}
}
}
</script>
File: view/frontend/layout/default.xml
Load your CSS globally across all pages.
<?xml version="1.0"?>
<!--
/**
* Copyright © Vendor. All rights reserved.
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head>
<css src="Vendor_ModuleName::css/widget-style.css"/>
</head>
</page>
File: view/frontend/web/css/widget-style.css
/**
* Copyright © Vendor. All rights reserved.
*/
/* Widget Container */
.custom-widget {
padding: 20px;
margin: 10px 0;
}
.custom-widget .widget-content {
background: #f5f5f5;
padding: 15px;
border-radius: 5px;
}
.custom-widget h3 {
margin: 0 0 10px;
font-size: 18px;
font-weight: 600;
}
.custom-widget .widget-button {
margin-top: 10px;
}
/* Responsive Design */
@media (max-width: 768px) {
.custom-widget {
padding: 15px;
}
.custom-widget .widget-content {
padding: 10px;
}
}
CSS Best Practices:
!important unless absolutely necessaryFor widgets that need to process data (forms, AJAX requests), add a controller.
File: etc/frontend/routes.xml
<?xml version="1.0"?>
<!--
/**
* Copyright © Vendor. All rights reserved.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="modulename" frontName="modulename">
<module name="Vendor_ModuleName" />
</route>
</router>
</config>
Route URL Pattern:
https://yourstore.com/modulename/index/submitmodulename = frontNameindex = controller directorysubmit = action file nameFile: Controller/Index/Submit.php
<?php
/**
* Copyright © Vendor. All rights reserved.
*/
declare(strict_types=1);
namespace Vendor\ModuleName\Controller\Index;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Exception\LocalizedException;
use Psr\Log\LoggerInterface;
/**
* Handle form submission
*/
class Submit implements HttpPostActionInterface
{
/**
* @var RequestInterface
*/
private $request;
/**
* @var JsonFactory
*/
private $resultJsonFactory;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @param RequestInterface $request
* @param JsonFactory $resultJsonFactory
* @param LoggerInterface $logger
*/
public function __construct(
RequestInterface $request,
JsonFactory $resultJsonFactory,
LoggerInterface $logger
) {
$this->request = $request;
$this->resultJsonFactory = $resultJsonFactory;
$this->logger = $logger;
}
/**
* Execute action
*
* @return \Magento\Framework\Controller\Result\Json
*/
public function execute()
{
$resultJson = $this->resultJsonFactory->create();
if (!$this->request->isPost()) {
return $resultJson->setData([
'success' => false,
'message' => __('Invalid request method.')
]);
}
try {
$postData = $this->request->getPostValue();
// Validate data
$this->validateData($postData);
// Process your data here
// Example: Save to database, send email, etc.
return $resultJson->setData([
'success' => true,
'message' => __('Your request has been submitted successfully.')
]);
} catch (LocalizedException $e) {
$this->logger->error('Widget form error: ' . $e->getMessage());
return $resultJson->setData([
'success' => false,
'message' => $e->getMessage()
]);
} catch (\Exception $e) {
$this->logger->error('Widget form error: ' . $e->getMessage());
return $resultJson->setData([
'success' => false,
'message' => __('An error occurred. Please try again later.')
]);
}
}
/**
* Validate form data
*
* @param array $data
* @throws LocalizedException
*/
private function validateData(array $data): void
{
if (empty($data['field_name'])) {
throw new LocalizedException(__('Field name is required.'));
}
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
throw new LocalizedException(__('Please enter a valid email address.'));
}
}
}
Controller Best Practices:
HttpPostActionInterface for POST requestsHttpGetActionInterface for GET requestsJsonFactory, PageFactory, RedirectFactory)For complex interactions like modal forms:
Template with Modal:
<?php
$modalId = 'modal-' . uniqid();
?>
<div class="widget-container">
<button type="button"
class="action primary"
data-mage-init='{"widgetModal": {"modalId": "<?= $escaper->escapeJs($modalId) ?>"}}'>
<?= $escaper->escapeHtml(__('Open Modal')) ?>
</button>
</div>
<!-- Modal Structure -->
<div id="<?= $escaper->escapeHtmlAttr($modalId) ?>"
class="widget-modal"
style="display: none;"
data-role="widget-modal">
<div class="widget-modal-overlay"></div>
<div class="widget-modal-content">
<div class="widget-modal-header">
<h2><?= $escaper->escapeHtml(__('Modal Title')) ?></h2>
<button type="button" class="widget-modal-close" aria-label="Close">
<span>×</span>
</button>
</div>
<div class="widget-modal-body">
<form id="widget-form" method="post">
<!-- Form fields -->
<input type="text" name="field_name" required />
<button type="submit" class="action primary">
<?= $escaper->escapeHtml(__('Submit')) ?>
</button>
</form>
</div>
</div>
</div>
Modal JavaScript:
IMPORTANT:
Magento_Ui/js/modal/modal component when you have custom modal HTML structure - it creates conflicts with z-index, positioning, and double overlays<body> on initialization to prevent parent container constraints (overflow, positioning, z-index)define([
'jquery'
], function ($) {
'use strict';
$.widget('vendor.widgetModal', {
options: {
modalId: ''
},
_create: function () {
this._moveModalToBody();
this._bind();
},
/**
* Move modal element to body to prevent parent container constraints
* This is CRITICAL - without this, modal will be trapped inside widget container
*/
_moveModalToBody: function () {
var modalElement = $('#' + this.options.modalId);
if (modalElement.length && modalElement.parent()[0].tagName !== 'BODY') {
// Move modal to body so it's not constrained by parent positioning
modalElement.appendTo('body');
}
},
_bind: function () {
var self = this;
// Open modal on button click
this.element.on('click', function (e) {
e.preventDefault();
self.openModal();
});
},
openModal: function () {
var modalElement = $('#' + this.options.modalId);
if (modalElement.length) {
// Show modal with fade effect
modalElement.fadeIn(300);
$('body').addClass('modal-open');
// Bind close button (only once)
modalElement.find('.widget-modal-close, .action.cancel').off('click').on('click', function (e) {
e.preventDefault();
modalElement.fadeOut(300);
$('body').removeClass('modal-open');
});
// Bind overlay click (only once)
modalElement.find('.widget-modal-overlay').off('click').on('click', function (e) {
e.preventDefault();
modalElement.fadeOut(300);
$('body').removeClass('modal-open');
});
// Bind ESC key
$(document).off('keyup.widgetModal').on('keyup.widgetModal', function (e) {
if (e.key === 'Escape' || e.keyCode === 27) {
modalElement.fadeOut(300);
$('body').removeClass('modal-open');
$(document).off('keyup.widgetModal');
}
});
}
}
});
return $.vendor.widgetModal;
});
Modal CSS Styling:
/**
* Modal Styling
* IMPORTANT:
* - Use very high z-index (999999) with !important to ensure modal appears above all content
* - Many themes use high z-index values for headers, menus, etc. (10000+)
* - Modal container and all children use position: fixed to escape parent containers
* - Overlay uses darker background (0.7 opacity) to clearly indicate blocked content
*/
.widget-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999999 !important;
}
.widget-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 999998 !important;
cursor: pointer;
}
.widget-modal-content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
z-index: 999999 !important;
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
/* Prevent body scroll when modal is open */
body.modal-open {
overflow: hidden;
}
/* Ensure modal container blocks all pointer events to elements below */
.widget-modal {
pointer-events: auto;
}
.widget-modal * {
pointer-events: auto;
}
.widget-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 30px;
border-bottom: 1px solid #e0e0e0;
}
.widget-modal-header h2 {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #333;
}
.widget-modal-close {
background: none;
border: none;
font-size: 32px;
line-height: 1;
color: #666;
cursor: pointer;
padding: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.3s ease;
}
.widget-modal-close:hover {
color: #000;
}
.widget-modal-body {
padding: 30px;
}
/* Responsive Design */
@media (max-width: 768px) {
.widget-modal-content {
width: 95%;
max-height: 95vh;
}
.widget-modal-header {
padding: 15px 20px;
}
.widget-modal-header h2 {
font-size: 20px;
}
.widget-modal-body {
padding: 20px;
}
}
# Enable the module
bin/magento module:enable Vendor_ModuleName
# Run setup upgrade
bin/magento setup:upgrade
# Compile dependency injection (production mode)
bin/magento setup:di:compile
# Deploy static content (production mode)
bin/magento setup:static-content:deploy -f
# Clear cache
bin/magento cache:flush
setup:upgrade)setup:di:compile)Add widget code directly in CMS content:
{{widget type="Vendor\ModuleName\Block\Widget\WidgetName"
text_param="My Value"
select_param="value1"
enabled="1"}}
Add widget programmatically in layout XML:
<referenceContainer name="content">
<block class="Vendor\ModuleName\Block\Widget\WidgetName"
name="custom.widget"
template="Vendor_ModuleName::widget/template.phtml">
<arguments>
<argument name="text_param" xsi:type="string">My Value</argument>
<argument name="select_param" xsi:type="string">value1</argument>
<argument name="enabled" xsi:type="boolean">true</argument>
</arguments>
</block>
</referenceContainer>
Create widget block in any template:
<?= $block->getLayout()
->createBlock(\Vendor\ModuleName\Block\Widget\WidgetName::class)
->setData('text_param', 'My Value')
->setData('enabled', true)
->toHtml() ?>
Based on the ItTools_QuoteForm module created for LCD Screen Repair:
Features:
Key Files:
app/code/ItTools/QuoteForm/
├── Block/Widget/QuoteButton.php
├── Controller/Index/Submit.php
├── etc/widget.xml
├── etc/email_templates.xml
├── view/frontend/templates/widget/quotebutton.phtml
├── view/frontend/web/js/quote-modal.js
├── view/frontend/web/js/quote-form.js
└── view/frontend/web/css/quote-form.css
Usage:
{{widget type="ItTools\QuoteForm\Block\Widget\QuoteButton"
button_text="Get a Free Quote"
button_class="action primary"}}
declare(strict_types=1);)Symptoms: Widget doesn't show in "Insert Widget" dropdown
Solutions:
widget.xml syntax (validate XML)bin/magento module:statusbin/magento cache:flushrm -rf generated/code/*system.log for errorsSymptoms: Widget code shows but no output
Solutions:
bin/magento cache:flushexception.log and system.logSymptoms: Widget functionality not working
Solutions:
requirejs-config.js syntaxbin/magento setup:static-content:deploy -fSymptoms: Widget appears unstyled
Solutions:
layout/default.xmlbin/magento setup:static-content:deploy -fSymptoms: AJAX returns errors or no response
Solutions:
exception.log for server errorsSymptoms: Files not uploading or validation fails
Solutions:
upload_max_filesize and post_max_sizeenctype="multipart/form-data"$_FILES array in controllerSymptoms: Modal is half-hidden, trapped inside section/div, has z-index issues, double overlays, or positioning problems
Root Causes:
Magento_Ui/js/modal/modal component and custom modal HTML structureSolutions:
modalElement.appendTo('body') in widget initialization to escape parent containersfadeIn()/fadeOut() instead of modal('openModal')position: fixed on BOTH overlay and content (not absolute)rgba(0, 0, 0, 0.7) to clearly block contentbody.modal-open { overflow: hidden; } to prevent background scrollpointer-events: auto to modal and children to block clicksrm -rf pub/static/frontend/*Example Fix:
// WRONG - causes conflicts
define(['jquery', 'Magento_Ui/js/modal/modal'], function ($, modal) {
modalElement.modal({ ... });
});
// CORRECT - simple and works
define(['jquery'], function ($) {
modalElement.fadeIn(300);
$('body').addClass('modal-open');
});
Before deploying your widget:
Real working examples from this codebase:
ItTools_QuoteForm (app/code/ItTools/QuoteForm/)
ItTools_CategorySearch (app/code/ItTools/CategorySearch/)
Use these as reference implementations.
vendor/magento/module-widget/vendor/magento/module-cms/Block/Widget/vendor/magento/module-catalog/Block/Widget/This skill is compatible with:
Not compatible with:
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience