diff --git a/packages/backend/src/apps/miro/actions/create-card-widget/index.ts b/packages/backend/src/apps/miro/actions/create-card-widget/index.ts
new file mode 100644
index 00000000..c1cb4746
--- /dev/null
+++ b/packages/backend/src/apps/miro/actions/create-card-widget/index.ts
@@ -0,0 +1,168 @@
+import defineAction from '../../../../helpers/define-action';
+
+type Body = {
+ data: {
+ title: string;
+ description?: string;
+ dueDate?: string;
+ };
+ style?: {
+ cardTheme?: string;
+ };
+ parent: {
+ id: string;
+ };
+};
+
+export default defineAction({
+ name: 'Create card widget',
+ key: 'createCardWidget',
+ description: 'Creates a new card widget on an existing board.',
+ arguments: [
+ {
+ label: 'Board',
+ key: 'boardId',
+ type: 'dropdown' as const,
+ required: true,
+ description: '',
+ variables: true,
+ source: {
+ type: 'query',
+ name: 'getDynamicData',
+ arguments: [
+ {
+ name: 'key',
+ value: 'listBoards',
+ },
+ ],
+ },
+ },
+ {
+ label: 'Frame',
+ key: 'frameId',
+ type: 'dropdown' as const,
+ required: true,
+ dependsOn: ['parameters.boardId'],
+ description:
+ 'You need to create a frame prior to this step. Switch frame to grid mode to avoid cards overlap.',
+ variables: true,
+ source: {
+ type: 'query',
+ name: 'getDynamicData',
+ arguments: [
+ {
+ name: 'key',
+ value: 'listFrames',
+ },
+ {
+ name: 'parameters.boardId',
+ value: '{parameters.boardId}',
+ },
+ ],
+ },
+ },
+ {
+ label: 'Card Title',
+ key: 'cardTitle',
+ type: 'string' as const,
+ required: true,
+ description: '',
+ variables: true,
+ },
+ {
+ label: 'Card Title Link',
+ key: 'cardTitleLink',
+ type: 'string' as const,
+ required: false,
+ description: '',
+ variables: true,
+ },
+ {
+ label: 'Card Description',
+ key: 'cardDescription',
+ type: 'string' as const,
+ required: false,
+ description: '',
+ variables: true,
+ },
+ {
+ label: 'Card Due Date',
+ key: 'cardDueDate',
+ type: 'string' as const,
+ required: false,
+ description:
+ 'format: date-time. Example value: 2023-10-12 22:00:55+00:00',
+ variables: true,
+ },
+ {
+ label: 'Card Border Color',
+ key: 'cardBorderColor',
+ type: 'dropdown' as const,
+ required: false,
+ description: 'In hex format. Default is blue (#2399F3).',
+ variables: true,
+ options: [
+ { label: 'white', value: '#FFFFFF' },
+ { label: 'yellow', value: '#FEF445' },
+ { label: 'orange', value: '#FAC710' },
+ { label: 'red', value: '#F24726' },
+ { label: 'bright red', value: '#DA0063' },
+ { label: 'light gray', value: '#E6E6E6' },
+ { label: 'gray', value: '#808080' },
+ { label: 'black', value: '#1A1A1A' },
+ { label: 'light green', value: '#CEE741' },
+ { label: 'green', value: '#8FD14F' },
+ { label: 'dark green', value: '#0CA789' },
+ { label: 'light blue', value: '#12CDD4' },
+ { label: 'blue', value: '#2D9BF0' },
+ { label: 'dark blue', value: '#414BB2' },
+ { label: 'purple', value: '#9510AC' },
+ { label: 'dark purple', value: '#652CB3' },
+ ],
+ },
+ ],
+
+ async run($) {
+ const {
+ boardId,
+ frameId,
+ cardTitle,
+ cardTitleLink,
+ cardDescription,
+ cardDueDate,
+ cardBorderColor,
+ } = $.step.parameters;
+
+ let title;
+ if (cardTitleLink) {
+ title = `${cardTitle}`;
+ } else {
+ title = cardTitle;
+ }
+
+ const body: Body = {
+ data: {
+ title: title as string,
+ description: cardDescription as string,
+ },
+ style: {},
+ parent: {
+ id: frameId as string,
+ },
+ };
+
+ if (cardBorderColor) {
+ body.style.cardTheme = cardBorderColor as string;
+ }
+
+ if (cardDueDate) {
+ body.data.dueDate = cardDueDate as string;
+ }
+
+ const response = await $.http.post(`/v2/boards/${boardId}/cards`, body);
+
+ $.setActionItem({
+ raw: response.data,
+ });
+ },
+});
diff --git a/packages/backend/src/apps/miro/actions/index.ts b/packages/backend/src/apps/miro/actions/index.ts
index 24f3edaf..896dd73f 100644
--- a/packages/backend/src/apps/miro/actions/index.ts
+++ b/packages/backend/src/apps/miro/actions/index.ts
@@ -1,4 +1,5 @@
import copyBoard from './copy-board';
import createBoard from './create-board';
+import createCardWidget from './create-card-widget';
-export default [copyBoard, createBoard];
+export default [copyBoard, createBoard, createCardWidget];
diff --git a/packages/backend/src/apps/miro/dynamic-data/index.ts b/packages/backend/src/apps/miro/dynamic-data/index.ts
index b1d11dac..ade1d40a 100644
--- a/packages/backend/src/apps/miro/dynamic-data/index.ts
+++ b/packages/backend/src/apps/miro/dynamic-data/index.ts
@@ -1,3 +1,4 @@
import listBoards from './list-boards';
+import listFrames from './list-frames';
-export default [listBoards];
+export default [listBoards, listFrames];
diff --git a/packages/backend/src/apps/miro/dynamic-data/list-frames/index.ts b/packages/backend/src/apps/miro/dynamic-data/list-frames/index.ts
new file mode 100644
index 00000000..8a234c1a
--- /dev/null
+++ b/packages/backend/src/apps/miro/dynamic-data/list-frames/index.ts
@@ -0,0 +1,44 @@
+import { IGlobalVariable, IJSONObject } from '@automatisch/types';
+
+export default {
+ name: 'List frames',
+ key: 'listFrames',
+
+ async run($: IGlobalVariable) {
+ const frames: {
+ data: IJSONObject[];
+ } = {
+ data: [],
+ };
+
+ const boardId = $.step.parameters.boardId;
+
+ if (!boardId) {
+ return { data: [] };
+ }
+
+ let next;
+ do {
+ const {
+ data: { data, links },
+ } = await $.http.get(`/v2/boards/${boardId}/items`);
+
+ next = links?.next;
+
+ const allFrames = data.filter(
+ (item: IJSONObject) => item.type === 'frame'
+ );
+
+ if (allFrames.length) {
+ for (const frame of allFrames) {
+ frames.data.push({
+ value: frame.id,
+ name: frame.data.title,
+ });
+ }
+ }
+ } while (next);
+
+ return frames;
+ },
+};
diff --git a/packages/docs/pages/apps/miro/actions.md b/packages/docs/pages/apps/miro/actions.md
index b20693f3..6cb8da69 100644
--- a/packages/docs/pages/apps/miro/actions.md
+++ b/packages/docs/pages/apps/miro/actions.md
@@ -5,6 +5,8 @@ items:
desc: Creates a new board.
- name: Copy board
desc: Creates a copy of an existing board.
+ - name: Create card widget
+ desc: Creates a new card widget on an existing board.
---