diff --git a/lib/util/schema.spec.ts b/lib/util/schema.spec.ts
index 1b15920c73dcca28257856b3663c25c3bf07d53a..a8c4221144cd5a29165d08f9079e8b9bc24271c2 100644
--- a/lib/util/schema.spec.ts
+++ b/lib/util/schema.spec.ts
@@ -81,4 +81,74 @@ describe('util/schema', () => {
       );
     });
   });
+
+  describe('looseArray', () => {
+    it('parses array', () => {
+      const s = schema.looseArray(z.string());
+      expect(s.parse(['foo', 'bar'])).toEqual(['foo', 'bar']);
+    });
+
+    it('handles non-array', () => {
+      const s = schema.looseArray(z.string());
+      expect(s.parse({ foo: 'bar' })).toEqual([]);
+    });
+
+    it('drops wrong items', () => {
+      const s = schema.looseArray(z.string());
+      expect(s.parse(['foo', 123, null, undefined, []])).toEqual(['foo']);
+    });
+
+    it('runs callback for wrong elements', () => {
+      let called = false;
+      const s = schema.looseArray(z.string(), () => {
+        called = true;
+      });
+      expect(s.parse(['foo', 123, 'bar'])).toEqual(['foo', 'bar']);
+      expect(called).toBeTrue();
+    });
+
+    it('runs callback for non-array', () => {
+      let called = false;
+      const s = schema.looseArray(z.string(), () => {
+        called = true;
+      });
+      expect(s.parse('foobar')).toEqual([]);
+      expect(called).toBeTrue();
+    });
+  });
+
+  describe('looseRecord', () => {
+    it('parses record', () => {
+      const s = schema.looseRecord(z.string());
+      expect(s.parse({ foo: 'bar' })).toEqual({ foo: 'bar' });
+    });
+
+    it('handles non-record', () => {
+      const s = schema.looseRecord(z.string());
+      expect(s.parse(['foo', 'bar'])).toEqual({});
+    });
+
+    it('drops wrong items', () => {
+      const s = schema.looseRecord(z.string());
+      expect(s.parse({ foo: 'foo', bar: 123 })).toEqual({ foo: 'foo' });
+    });
+
+    it('runs callback for wrong elements', () => {
+      let called = false;
+      const s = schema.looseRecord(z.string(), () => {
+        called = true;
+      });
+      expect(s.parse({ foo: 'foo', bar: 123 })).toEqual({ foo: 'foo' });
+      expect(called).toBeTrue();
+    });
+
+    it('runs callback for non-record', () => {
+      let called = false;
+      const s = schema.looseRecord(z.string(), () => {
+        called = true;
+      });
+      expect(s.parse('foobar')).toEqual({});
+      expect(called).toBeTrue();
+    });
+  });
 });
diff --git a/lib/util/schema.ts b/lib/util/schema.ts
index 8e874cb957ed98bca50fc53df68293625378b837..7b0756def41bea71eed2e0a4b03411ecf95f5a15 100644
--- a/lib/util/schema.ts
+++ b/lib/util/schema.ts
@@ -1,5 +1,6 @@
+import is from '@sindresorhus/is';
 import hasha from 'hasha';
-import type { z } from 'zod';
+import { z } from 'zod';
 import { logger } from '../logger';
 import * as memCache from './cache/memory';
 import { safeStringify } from './stringify';
@@ -65,3 +66,80 @@ export function match<T extends z.ZodSchema>(
 
   return true;
 }
+
+export function looseArray<T extends z.ZodTypeAny>(
+  schema: T,
+  catchCallback?: () => void
+): z.ZodEffects<
+  z.ZodCatch<z.ZodArray<z.ZodCatch<z.ZodNullable<T>>, 'many'>>,
+  z.TypeOf<T>[],
+  unknown
+> {
+  type Elem = z.infer<T>;
+
+  const nullableSchema = schema.nullable().catch(
+    catchCallback
+      ? () => {
+          catchCallback();
+          return null;
+        }
+      : null
+  );
+
+  const arrayOfNullables = z.array(nullableSchema);
+
+  const arrayWithFallback = catchCallback
+    ? arrayOfNullables.catch(() => {
+        catchCallback();
+        return [];
+      })
+    : arrayOfNullables.catch([]);
+
+  const filteredArray = arrayWithFallback.transform((xs) =>
+    xs.filter((x): x is Elem => !is.null_(x))
+  );
+
+  return filteredArray;
+}
+
+export function looseRecord<T extends z.ZodTypeAny>(
+  schema: T,
+  catchCallback?: () => void
+): z.ZodEffects<
+  z.ZodCatch<z.ZodRecord<z.ZodString, z.ZodCatch<z.ZodNullable<T>>>>,
+  Record<string, z.TypeOf<T>>,
+  unknown
+> {
+  type Elem = z.infer<T>;
+
+  const nullableSchema = schema.nullable().catch(
+    catchCallback
+      ? () => {
+          catchCallback();
+          return null;
+        }
+      : null
+  );
+
+  const recordOfNullables = z.record(nullableSchema);
+
+  const recordWithFallback = catchCallback
+    ? recordOfNullables.catch(() => {
+        catchCallback();
+        return {};
+      })
+    : recordOfNullables.catch({});
+
+  const filteredRecord = recordWithFallback.transform(
+    (rec): Record<string, Elem> => {
+      for (const key of Object.keys(rec)) {
+        if (is.null_(rec[key])) {
+          delete rec[key];
+        }
+      }
+      return rec;
+    }
+  );
+
+  return filteredRecord;
+}