diff --git a/package.json b/package.json
index cf1ad36db..e89df64f8 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
+ "cron-parser": "^4.9.0",
"currency-codes": "^2.2.0",
"date-fns": "^4.1.0",
"decimal.js": "^10.6.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 06120631e..44a6c2ade 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -120,6 +120,9 @@ importers:
cmdk:
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ cron-parser:
+ specifier: ^4.9.0
+ version: 4.9.0
currency-codes:
specifier: ^2.2.0
version: 2.2.0
@@ -4093,6 +4096,13 @@ packages:
integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==,
}
+ cron-parser@4.9.0:
+ resolution:
+ {
+ integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==,
+ }
+ engines: { node: '>=12.0.0' }
+
cross-spawn@7.0.6:
resolution:
{
@@ -6576,6 +6586,13 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ luxon@3.7.2:
+ resolution:
+ {
+ integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==,
+ }
+ engines: { node: '>=12' }
+
magic-string@0.30.21:
resolution:
{
@@ -11697,6 +11714,10 @@ snapshots:
crelt@1.0.6: {}
+ cron-parser@4.9.0:
+ dependencies:
+ luxon: 3.7.2
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -13309,6 +13330,8 @@ snapshots:
dependencies:
react: 19.2.4
+ luxon@3.7.2: {}
+
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
diff --git a/src/components/functions/ConfigForm.tsx b/src/components/functions/ConfigForm.tsx
index b49303448..73f5f1e4f 100644
--- a/src/components/functions/ConfigForm.tsx
+++ b/src/components/functions/ConfigForm.tsx
@@ -132,7 +132,12 @@ export const ConfigForm = ({ middlewares, data }: ConfigFormProps) => {
)}
{form.watch('functionType') === 'cron' && (
-
+
)}
{(form.watch('functionType') === 'webhook' ||
form.watch('functionType') === 'request') && (
diff --git a/src/components/functions/CreateFunctionForm.tsx b/src/components/functions/CreateFunctionForm.tsx
index d792ae6b2..400280b7c 100644
--- a/src/components/functions/CreateFunctionForm.tsx
+++ b/src/components/functions/CreateFunctionForm.tsx
@@ -144,6 +144,7 @@ export const CreateFunctionForm = ({
functionType: data.functionType,
timeout: data.timeout,
inputs: {
+ cronPattern: options.cronString,
event: options.cronString,
},
})
diff --git a/src/components/functions/EditFunctionForm.tsx b/src/components/functions/EditFunctionForm.tsx
index fe4a781ad..d486e6a28 100644
--- a/src/components/functions/EditFunctionForm.tsx
+++ b/src/components/functions/EditFunctionForm.tsx
@@ -69,7 +69,10 @@ export const EditFunctionForm = ({
? {
options: {
functionType: functionData.functionType,
- cronString: functionData.inputs?.event!,
+ cronString:
+ functionData.inputs?.cronPattern ??
+ functionData.inputs?.event ??
+ '',
},
}
: {}),
@@ -186,6 +189,7 @@ export const EditFunctionForm = ({
functionType: data.functionType,
timeout: data.timeout,
inputs: {
+ cronPattern: options.cronString,
event: options.cronString,
},
})
diff --git a/src/components/functions/zod.ts b/src/components/functions/zod.ts
index 879e4001e..52c8b1188 100644
--- a/src/components/functions/zod.ts
+++ b/src/components/functions/zod.ts
@@ -1,4 +1,14 @@
import { z } from 'zod';
+import { parseExpression } from 'cron-parser';
+
+function validateCronString(value: string): boolean {
+ try {
+ parseExpression(value.trim(), { tz: 'UTC' });
+ return true;
+ } catch {
+ return false;
+ }
+}
export enum ParamsEnum {
String = 'string',
@@ -126,7 +136,13 @@ export const EventOptions = z.object({
});
export const CronOptions = z.object({
functionType: z.literal('cron'),
- cronString: z.string(),
+ cronString: z
+ .string()
+ .min(1, 'Cron schedule is required')
+ .refine(validateCronString, {
+ message:
+ 'Invalid cron pattern. Use 5 fields: minute hour day month weekday (UTC). Example: */5 * * * *',
+ }),
});
export const SocketOptions = z.object({
functionType: z.literal('socket'),
diff --git a/src/lib/models/functions/schemas.ts b/src/lib/models/functions/schemas.ts
index b12c1ea7d..cda692c14 100644
--- a/src/lib/models/functions/schemas.ts
+++ b/src/lib/models/functions/schemas.ts
@@ -1,6 +1,7 @@
interface IWebInputsInterface {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
event?: string;
+ cronPattern?: string;
bodyParams?: { [key: string]: any }[];
urlParams?: { [key: string]: any }[];
queryParams?: { [key: string]: any }[];