diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index 54ece3ce5218361279792aa94e99f818f0fc0f65..9101b42677705427ffb731439fdce91b3eb13519 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -663,7 +663,7 @@ As this is a template it can be dynamically set. E.g. add the `packageName` as p
 ### format
 
 Defines which format the API is returning.
-Only `json` is supported, but more are planned for future.
+Currently `json` or `plain` are supported, see the `custom` [datasource documentation](/modules/datasource/custom/) for more information.
 
 ### transformTemplates
 
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 6c51005969b98278fca00811a20137b6cd61d955..77a57330c21963ac21ebac6682e5d749b7f5ac79 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -74,7 +74,7 @@ const options: RenovateOptions[] = [
     type: 'string',
     parent: 'customDatasources',
     default: 'json',
-    allowedValues: ['json'],
+    allowedValues: ['json', 'plain'],
     cli: false,
     env: false,
   },
diff --git a/lib/config/types.ts b/lib/config/types.ts
index 96bf67fa9e58d7415353900f38243a993ff9b058..58a36ab50155ca493bd11402463eea5493d2b313 100644
--- a/lib/config/types.ts
+++ b/lib/config/types.ts
@@ -277,7 +277,7 @@ export interface RenovateConfig
 
 export interface CustomDatasourceConfig {
   defaultRegistryUrlTemplate?: string;
-  format?: 'json';
+  format?: 'json' | 'plain';
   transformTemplates?: string[];
 }
 
diff --git a/lib/modules/datasource/custom/index.spec.ts b/lib/modules/datasource/custom/index.spec.ts
index 901c6f16515dc47bfe67b4940d6619e3818c41b8..e6edfe1227c53a8aa78b76039064ed44651bde34 100644
--- a/lib/modules/datasource/custom/index.spec.ts
+++ b/lib/modules/datasource/custom/index.spec.ts
@@ -84,6 +84,120 @@ describe('modules/datasource/custom/index', () => {
       expect(result).toEqual(expected);
     });
 
+    it('return releases for plain text API directly exposing in Renovate format', async () => {
+      const expected = {
+        releases: [
+          {
+            version: '1.0.0',
+          },
+          {
+            version: '2.0.0',
+          },
+          {
+            version: '3.0.0',
+          },
+        ],
+      };
+      httpMock
+        .scope('https://example.com')
+        .get('/v1')
+        .reply(200, '1.0.0\n2.0.0\n3.0.0', {
+          'Content-Type': 'text/plain',
+        });
+      const result = await getPkgReleases({
+        datasource: `${CustomDatasource.id}.foo`,
+        packageName: 'myPackage',
+        customDatasources: {
+          foo: {
+            defaultRegistryUrlTemplate: 'https://example.com/v1',
+            format: 'plain',
+          },
+        },
+      });
+      expect(result).toEqual(expected);
+    });
+
+    it('return releases for plain text API and trim the content', async () => {
+      const expected = {
+        releases: [
+          {
+            version: '1.0.0',
+          },
+          {
+            version: '2.0.0',
+          },
+          {
+            version: '3.0.0',
+          },
+        ],
+      };
+      httpMock
+        .scope('https://example.com')
+        .get('/v1')
+        .reply(200, '1.0.0 \n2.0.0 \n 3.0.0 ', {
+          'Content-Type': 'text/plain',
+        });
+      const result = await getPkgReleases({
+        datasource: `${CustomDatasource.id}.foo`,
+        packageName: 'myPackage',
+        customDatasources: {
+          foo: {
+            defaultRegistryUrlTemplate: 'https://example.com/v1',
+            format: 'plain',
+          },
+        },
+      });
+      expect(result).toEqual(expected);
+    });
+
+    it('return releases for plain text API when only returns a single version', async () => {
+      const expected = {
+        releases: [
+          {
+            version: '1.0.0',
+          },
+        ],
+      };
+      httpMock.scope('https://example.com').get('/v1').reply(200, '1.0.0', {
+        'Content-Type': 'text/plain',
+      });
+      const result = await getPkgReleases({
+        datasource: `${CustomDatasource.id}.foo`,
+        packageName: 'myPackage',
+        customDatasources: {
+          foo: {
+            defaultRegistryUrlTemplate: 'https://example.com/v1',
+            format: 'plain',
+          },
+        },
+      });
+      expect(result).toEqual(expected);
+    });
+
+    it('return null for plain text API if the body is not what is expected', async () => {
+      const expected = {
+        releases: [
+          {
+            version: '1.0.0',
+          },
+        ],
+      };
+      httpMock.scope('https://example.com').get('/v1').reply(200, expected, {
+        'Content-Type': 'application/json',
+      });
+      const result = await getPkgReleases({
+        datasource: `${CustomDatasource.id}.foo`,
+        packageName: 'myPackage',
+        customDatasources: {
+          foo: {
+            defaultRegistryUrlTemplate: 'https://example.com/v1',
+            format: 'plain',
+          },
+        },
+      });
+      expect(result).toBeNull();
+    });
+
     it('return release when templating registryUrl', async () => {
       const expected = {
         releases: [
diff --git a/lib/modules/datasource/custom/index.ts b/lib/modules/datasource/custom/index.ts
index 4a4906c670014c6f7789ac206788aa3f6f403a50..cb1739fd827e246953300c7f0582b403637f2d8f 100644
--- a/lib/modules/datasource/custom/index.ts
+++ b/lib/modules/datasource/custom/index.ts
@@ -1,6 +1,7 @@
 import is from '@sindresorhus/is';
 import jsonata from 'jsonata';
 import { logger } from '../../../logger';
+import { newlineRegex } from '../../../util/regex';
 import { Datasource } from '../datasource';
 import type { GetReleasesConfig, ReleaseResult } from '../types';
 import { ReleaseResultZodSchema } from './schema';
@@ -38,11 +39,15 @@ export class CustomDatasource extends Datasource {
       return null;
     }
 
-    const { defaultRegistryUrlTemplate, transformTemplates } = config;
-    // TODO add here other format options than JSON
+    const { defaultRegistryUrlTemplate, transformTemplates, format } = config;
+    // TODO add here other format options than JSON and "plain"
     let response: unknown;
     try {
-      response = (await this.http.getJson(defaultRegistryUrlTemplate)).body;
+      if (format === 'plain') {
+        response = await this.fetchPlainFormat(defaultRegistryUrlTemplate);
+      } else {
+        response = (await this.http.getJson(defaultRegistryUrlTemplate)).body;
+      }
     } catch (e) {
       this.handleHttpErrors(e);
       return null;
@@ -64,4 +69,24 @@ export class CustomDatasource extends Datasource {
       return null;
     }
   }
+
+  private async fetchPlainFormat(url: string): Promise<unknown> {
+    const response = await this.http.get(url, {
+      headers: {
+        Accept: 'text/plain',
+      },
+    });
+    const contentType = response.headers['content-type'];
+    if (!contentType?.startsWith('text/')) {
+      return null;
+    }
+    const versions = response.body.split(newlineRegex).map((version) => {
+      return {
+        version: version.trim(),
+      };
+    });
+    return {
+      releases: versions,
+    };
+  }
 }
diff --git a/lib/modules/datasource/custom/readme.md b/lib/modules/datasource/custom/readme.md
index 20d35d0246b4431447de88fae6479d8d6e607d1f..1585e996babe9ec7326f39acbc9dd3490c90376c 100644
--- a/lib/modules/datasource/custom/readme.md
+++ b/lib/modules/datasource/custom/readme.md
@@ -11,7 +11,7 @@ Options:
 | option                     | default | description                                                                                                                                                              |
 | -------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
 | defaultRegistryUrlTemplate | ""      | url used if no `registryUrl` is provided when looking up new releases                                                                                                    |
-| format                     | "json"  | format used by the API. Available values are: `json`                                                                                                                     |
+| format                     | "json"  | format used by the API. Available values are: `json`, `plain`                                                                                                            |
 | transformTemplates         | []      | [jsonata rules](https://docs.jsonata.org/simple) to transform the API output. Each rule will be evaluated after another and the result will be used as input to the next |
 
 Available template variables:
@@ -78,6 +78,46 @@ All available options:
 }
 ```
 
+### Formats
+
+#### JSON
+
+If `json` is used processing works as described above.
+The returned body will be directly interpreted as JSON and forwarded to the transformation rules.
+
+#### Plain
+
+If the format is set to `plain`, Renovate will call the HTTP endpoint with the `Accept` header value `text/plain`.
+The body of the response will be treated as plain text and will be converted into JSON.
+
+Suppose the body of the HTTP response is as follows::
+
+```
+1.0.0
+2.0.0
+3.0.0
+```
+
+When Renovate receives this response with the `plain` format, it will convert it into the following:
+
+```json
+{
+  "releases": [
+    {
+      "version": "1.0.0"
+    },
+    {
+      "version": "2.0.0"
+    },
+    {
+      "version": "3.0.0"
+    }
+  ]
+}
+```
+
+After the conversion, any `jsonata` rules defined in the `transformTemplates` section will be applied as usual to further process the JSON data.
+
 ## Examples
 
 ### K3s