diff --git a/lib/modules/datasource/packagist/index.spec.ts b/lib/modules/datasource/packagist/index.spec.ts
index 4ef75442e11e33227c2a323606f98ed25188319f..935c7443173efe4acf269b492deb434658785fe5 100644
--- a/lib/modules/datasource/packagist/index.spec.ts
+++ b/lib/modules/datasource/packagist/index.spec.ts
@@ -57,10 +57,10 @@ describe('modules/datasource/packagist/index', () => {
       const packagesOnly = {
         packages: {
           'vendor/package-name': {
-            'dev-master': {},
-            '1.0.x-dev': {},
-            '0.0.1': {},
-            '1.0.0': {},
+            'dev-master': { version: 'dev-master' },
+            '1.0.x-dev': { version: '1.0.x-dev' },
+            '0.0.1': { version: '0.0.1' },
+            '1.0.0': { version: '1.0.0' },
           },
         },
       };
@@ -146,7 +146,7 @@ describe('modules/datasource/packagist/index', () => {
         packages: [],
         includes: {
           'include/all$afbf74d51f31c7cbb5ff10304f9290bfb4f4e68b.json': {
-            sha1: 'afbf74d51f31c7cbb5ff10304f9290bfb4f4e68b',
+            sha256: 'afbf74d51f31c7cbb5ff10304f9290bfb4f4e68b',
           },
         },
       };
diff --git a/lib/modules/datasource/packagist/index.ts b/lib/modules/datasource/packagist/index.ts
index 9171467dc48c1d6d4b9dcfc22a0014e58d7b20fc..a8a2219516765361dd5f651cb880fd247531bfcf 100644
--- a/lib/modules/datasource/packagist/index.ts
+++ b/lib/modules/datasource/packagist/index.ts
@@ -1,4 +1,5 @@
 import URL from 'url';
+import is from '@sindresorhus/is';
 import { logger } from '../../../logger';
 import { ExternalHostError } from '../../../types/errors/external-host-error';
 import { cache } from '../../../util/cache/package/decorator';
@@ -11,13 +12,7 @@ import * as composerVersioning from '../../versioning/composer';
 import { Datasource } from '../datasource';
 import type { GetReleasesConfig, ReleaseResult } from '../types';
 import * as schema from './schema';
-import type {
-  AllPackages,
-  PackageMeta,
-  PackagistFile,
-  RegistryFile,
-  RegistryMeta,
-} from './types';
+import type { AllPackages, PackagistFile, RegistryFile } from './types';
 
 export class PackagistDatasource extends Datasource {
   static readonly id = 'packagist';
@@ -41,46 +36,12 @@ export class PackagistDatasource extends Datasource {
     return username && password ? { username, password } : {};
   }
 
-  private async getRegistryMeta(regUrl: string): Promise<RegistryMeta | null> {
+  private async getRegistryMeta(regUrl: string): Promise<schema.RegistryMeta> {
     const url = URL.resolve(ensureTrailingSlash(regUrl), 'packages.json');
     const opts = PackagistDatasource.getHostOpts(url);
-    const res = (await this.http.getJson<PackageMeta>(url, opts)).body;
-    const meta: RegistryMeta = {
-      providerPackages: {},
-      packages: res.packages,
-    };
-    if (res.includes) {
-      meta.includesFiles = [];
-      for (const [name, val] of Object.entries(res.includes)) {
-        const file = {
-          key: name.replace(val.sha256, '%hash%'),
-          sha256: val.sha256,
-        };
-        meta.includesFiles.push(file);
-      }
-    }
-    if (res['providers-url']) {
-      meta.providersUrl = res['providers-url'];
-    }
-    if (res['providers-lazy-url']) {
-      meta.providersLazyUrl = res['providers-lazy-url'];
-    }
-    if (res['provider-includes']) {
-      meta.files = [];
-      for (const [key, val] of Object.entries(res['provider-includes'])) {
-        const file = {
-          key,
-          sha256: val.sha256,
-        };
-        meta.files.push(file);
-      }
-    }
-    if (res.providers) {
-      for (const [key, val] of Object.entries(res.providers)) {
-        meta.providerPackages[key] = val.sha256;
-      }
-    }
-    return meta;
+    const { body } = await this.http.getJson(url, opts);
+    const res = schema.RegistryMeta.parse(body);
+    return res;
   }
 
   private static isPrivatePackage(regUrl: string): boolean {
@@ -186,7 +147,7 @@ export class PackagistDatasource extends Datasource {
       }
     }
     const allPackages: AllPackages = {
-      packages,
+      packages: packages as never, // TODO: fix types (#9610)
       providersUrl,
       providersLazyUrl,
       providerPackages,
@@ -248,13 +209,13 @@ export class PackagistDatasource extends Datasource {
         return includesPackages[packageName];
       }
       let pkgUrl: string;
-      if (packageName in providerPackages) {
-        pkgUrl = URL.resolve(
-          registryUrl,
-          providersUrl!
-            .replace('%package%', packageName)
-            .replace('%hash%', providerPackages[packageName])
-        );
+      const hash = providerPackages[packageName];
+      if (providersUrl && !is.undefined(hash)) {
+        let url = providersUrl.replace('%package%', packageName);
+        if (hash) {
+          url = url.replace('%hash%', hash);
+        }
+        pkgUrl = URL.resolve(registryUrl, url);
       } else if (providersLazyUrl) {
         pkgUrl = URL.resolve(
           registryUrl,
diff --git a/lib/modules/datasource/packagist/schema.ts b/lib/modules/datasource/packagist/schema.ts
index 4a5f379e128eb68e64f68daf83a746eb4cd19feb..2ed4e70e9221c64df5d03dc27c22abe390f5d021 100644
--- a/lib/modules/datasource/packagist/schema.ts
+++ b/lib/modules/datasource/packagist/schema.ts
@@ -144,3 +144,76 @@ export function parsePackagesResponses(
 
   return result;
 }
+
+const RegistryFile = z.object({
+  key: z.string(),
+  sha256: z.string(),
+});
+export type RegistryFile = z.infer<typeof RegistryFile>;
+
+const RegistryMetaFile = z.object({
+  sha256: z.string().nullable(),
+});
+type RegistryMetaFile = z.infer<typeof RegistryMetaFile>;
+
+const RegistryMetaFiles = z
+  .record(RegistryMetaFile.nullable().catch(null))
+  .transform((obj) => {
+    // Remove all null values
+    // TODO: extract as schema utility
+    const result: Record<string, RegistryMetaFile> = {};
+    for (const [key, val] of Object.entries(obj)) {
+      if (val !== null) {
+        result[key] = val;
+      }
+    }
+    return result;
+  });
+
+const RegistryMetaIncludes = RegistryMetaFiles.transform(
+  (obj): RegistryFile[] => {
+    const result: RegistryFile[] = [];
+    for (const [key, { sha256 }] of Object.entries(obj)) {
+      if (sha256) {
+        result.push({ key, sha256 });
+      }
+    }
+    return result;
+  }
+)
+  .nullable()
+  .catch(null);
+
+export const RegistryMeta = z
+  .object({
+    ['packages']: z.record(z.record(ComposerRelease)).nullable().catch(null),
+    ['includes']: RegistryMetaIncludes,
+    ['provider-includes']: RegistryMetaIncludes,
+    ['providers-url']: z.string().nullable().catch(null),
+    ['providers-lazy-url']: z.string().optional().nullable().catch(null),
+    ['providers']: RegistryMetaFiles.transform((obj) => {
+      const result: Record<string, string | null> = {};
+      for (const [key, { sha256 }] of Object.entries(obj)) {
+        result[key] = sha256;
+      }
+      return result;
+    }).catch({}),
+  })
+  .transform(
+    ({
+      ['packages']: packages,
+      ['includes']: includesFiles,
+      ['providers']: providerPackages,
+      ['provider-includes']: files,
+      ['providers-url']: providersUrl,
+      ['providers-lazy-url']: providersLazyUrl,
+    }) => ({
+      packages,
+      includesFiles,
+      providerPackages,
+      files,
+      providersUrl,
+      providersLazyUrl,
+    })
+  );
+export type RegistryMeta = z.infer<typeof RegistryMeta>;
diff --git a/lib/modules/datasource/packagist/types.ts b/lib/modules/datasource/packagist/types.ts
index fea795612e7fc3ff316c1a3fcc1576cb1026c8b8..317c1db50b81f7663a796e07cab4001813678822 100644
--- a/lib/modules/datasource/packagist/types.ts
+++ b/lib/modules/datasource/packagist/types.ts
@@ -1,13 +1,5 @@
 import type { ReleaseResult } from '../types';
 
-export interface PackageMeta {
-  includes?: Record<string, { sha256: string }>;
-  packages: Record<string, RegistryFile>;
-  'provider-includes': Record<string, { sha256: string }>;
-  providers: Record<string, { sha256: string }>;
-  'providers-lazy-url'?: string;
-  'providers-url'?: string;
-}
 export interface RegistryFile {
   key: string;
   sha256: string;
@@ -28,9 +20,9 @@ export interface PackagistFile {
 
 export interface AllPackages {
   packages: Record<string, RegistryFile>;
-  providersUrl?: string;
-  providersLazyUrl?: string;
-  providerPackages: Record<string, string>;
+  providersUrl: string | null;
+  providersLazyUrl: string | null;
+  providerPackages: Record<string, string | null>;
 
   includesPackages: Record<string, ReleaseResult>;
 }