diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts
index 939a55fe1c58d6b287e957c617f2a83d257026e7..7a9ca693431484b67235718d30118275bb9c88c4 100644
--- a/lib/modules/manager/api.ts
+++ b/lib/modules/manager/api.ts
@@ -6,6 +6,7 @@ import * as azurePipelines from './azure-pipelines';
 import * as batect from './batect';
 import * as batectWrapper from './batect-wrapper';
 import * as bazel from './bazel';
+import * as bazelModule from './bazel-module';
 import * as bazelisk from './bazelisk';
 import * as bicep from './bicep';
 import * as bitbucketPipelines from './bitbucket-pipelines';
@@ -96,6 +97,7 @@ api.set('azure-pipelines', azurePipelines);
 api.set('batect', batect);
 api.set('batect-wrapper', batectWrapper);
 api.set('bazel', bazel);
+api.set('bazel-module', bazelModule);
 api.set('bazelisk', bazelisk);
 api.set('bicep', bicep);
 api.set('bitbucket-pipelines', bitbucketPipelines);
diff --git a/lib/modules/manager/bazel-module/bazel-dep.spec.ts b/lib/modules/manager/bazel-module/bazel-dep.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2b4c022621910271972807b5b9013b14af4fdb0f
--- /dev/null
+++ b/lib/modules/manager/bazel-module/bazel-dep.spec.ts
@@ -0,0 +1,23 @@
+import { BazelDatasource } from '../../datasource/bazel';
+import { ToBazelDep } from './bazel-dep';
+import * as fragments from './fragments';
+
+describe('modules/manager/bazel-module/bazel-dep', () => {
+  describe('ToBazelDep', () => {
+    it('transforms a record fragment', () => {
+      const record = fragments.record({
+        rule: fragments.string('bazel_dep'),
+        name: fragments.string('rules_foo'),
+        version: fragments.string('1.2.3'),
+        dev_dependency: fragments.boolean(true),
+      });
+      const result = ToBazelDep.parse(record);
+      expect(result).toEqual({
+        datasource: BazelDatasource.id,
+        depType: 'bazel_dep',
+        depName: 'rules_foo',
+        currentValue: '1.2.3',
+      });
+    });
+  });
+});
diff --git a/lib/modules/manager/bazel-module/bazel-dep.ts b/lib/modules/manager/bazel-module/bazel-dep.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8d6f6c85e6cb14a8c296da1e923177db4b096f9a
--- /dev/null
+++ b/lib/modules/manager/bazel-module/bazel-dep.ts
@@ -0,0 +1,28 @@
+import { z } from 'zod';
+import { BazelDatasource } from '../../datasource/bazel';
+import type { PackageDependency } from '../types';
+import {
+  BooleanFragmentSchema,
+  RecordFragmentSchema,
+  StringFragmentSchema,
+} from './fragments';
+
+const BazelDepSchema = RecordFragmentSchema.extend({
+  children: z.object({
+    rule: StringFragmentSchema.extend({
+      value: z.literal('bazel_dep'),
+    }),
+    name: StringFragmentSchema,
+    version: StringFragmentSchema,
+    dev_dependency: BooleanFragmentSchema.optional(),
+  }),
+});
+
+export const ToBazelDep = BazelDepSchema.transform(
+  ({ children: { rule, name, version } }): PackageDependency => ({
+    datasource: BazelDatasource.id,
+    depType: rule.value,
+    depName: name.value,
+    currentValue: version.value,
+  })
+);
diff --git a/lib/modules/manager/bazel-module/context.spec.ts b/lib/modules/manager/bazel-module/context.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..06d22d0b35884e6b03f34115830f07680fcf7202
--- /dev/null
+++ b/lib/modules/manager/bazel-module/context.spec.ts
@@ -0,0 +1,98 @@
+import { Ctx, CtxProcessingError } from './context';
+import * as fragments from './fragments';
+
+describe('modules/manager/bazel-module/context', () => {
+  describe('Ctx', () => {
+    it('construct simple bazel_dep', () => {
+      const ctx = new Ctx()
+        .startRule('bazel_dep')
+        .startAttribute('name')
+        .addString('rules_foo')
+        .startAttribute('version')
+        .addString('1.2.3')
+        .endRule();
+
+      expect(ctx.results).toEqual([
+        fragments.record(
+          {
+            rule: fragments.string('bazel_dep'),
+            name: fragments.string('rules_foo'),
+            version: fragments.string('1.2.3'),
+          },
+          true
+        ),
+      ]);
+    });
+
+    it('construct a rule with array arg', () => {
+      const ctx = new Ctx()
+        .startRule('foo_library')
+        .startAttribute('name')
+        .addString('my_library')
+        .startAttribute('srcs')
+        .startArray()
+        .addString('first')
+        .addString('second')
+        .endArray()
+        .endRule();
+
+      expect(ctx.results).toEqual([
+        fragments.record(
+          {
+            rule: fragments.string('foo_library'),
+            name: fragments.string('my_library'),
+            srcs: fragments.array(
+              [fragments.string('first'), fragments.string('second')],
+              true
+            ),
+          },
+          true
+        ),
+      ]);
+    });
+
+    describe('.currentRecord', () => {
+      it('returns the record fragment if it is current', () => {
+        const ctx = new Ctx().startRecord();
+        expect(ctx.currentRecord).toEqual(fragments.record());
+      });
+
+      it('throws if there is no current', () => {
+        const ctx = new Ctx();
+        expect(() => ctx.currentRecord).toThrow(
+          new Error('Requested current, but no value.')
+        );
+      });
+
+      it('throws if the current is not a record fragment', () => {
+        const ctx = new Ctx().startArray();
+        expect(() => ctx.currentRecord).toThrow(
+          new Error('Requested current record, but does not exist.')
+        );
+      });
+    });
+
+    describe('.currentArray', () => {
+      it('returns the array fragment if it is current', () => {
+        const ctx = new Ctx().startArray();
+        expect(ctx.currentArray).toEqual(fragments.array());
+      });
+
+      it('throws if the current is not a record fragment', () => {
+        const ctx = new Ctx().startRecord();
+        expect(() => ctx.currentArray).toThrow(
+          new Error('Requested current array, but does not exist.')
+        );
+      });
+    });
+
+    it('throws if add an attribute without a parent', () => {
+      const ctx = new Ctx().startAttribute('name');
+      expect(() => ctx.addString('chicken')).toThrow(
+        new CtxProcessingError(
+          fragments.attribute('name', fragments.string('chicken'))
+        )
+      );
+    });
+  });
+});
diff --git a/lib/modules/manager/bazel-module/context.ts b/lib/modules/manager/bazel-module/context.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b590a560e026525202deeb55a593c1b2d18b6af8
--- /dev/null
+++ b/lib/modules/manager/bazel-module/context.ts
@@ -0,0 +1,154 @@
+import type {
+  AllFragments,
+  ArrayFragment,
+  ChildFragments,
+  RecordFragment,
+} from './fragments';
+import * as fragments from './fragments';
+
+// Represents the fields that the context must have.
+export interface CtxCompatible {
+  results: RecordFragment[];
+  stack: AllFragments[];
+}
+
+export class CtxProcessingError extends Error {
+  readonly current: AllFragments;
+  readonly parent?: AllFragments;
+  constructor(current: AllFragments, parent?: AllFragments) {
+    const msg = `Invalid context state. current: ${current.type}, parent: ${
+      parent?.type ?? 'none'
+    }`;
+    super(msg);
+    this.name = 'CtxProcessingError';
+    this.current = current;
+    this.parent = parent;
+  }
+}
+
+export class Ctx implements CtxCompatible {
+  results: RecordFragment[];
+  stack: AllFragments[];
+
+  constructor(results: RecordFragment[] = [], stack: AllFragments[] = []) {
+    this.results = results;
+    this.stack = stack;
+  }
+
+  private get safeCurrent(): AllFragments | undefined {
+    return this.stack.at(-1);
+  }
+
+  private get current(): AllFragments {
+    const c = this.safeCurrent;
+    if (c === undefined) {
+      throw new Error('Requested current, but no value.');
+    }
+    return c;
+  }
+  get currentRecord(): RecordFragment {
+    const current = this.current;
+    if (current.type === 'record') {
+      return current;
+    }
+    throw new Error('Requested current record, but does not exist.');
+  }
+
+  get currentArray(): ArrayFragment {
+    const current = this.current;
+    if (current.type === 'array') {
+      return current;
+    }
+    throw new Error('Requested current array, but does not exist.');
+  }
+
+  private popStack(): boolean {
+    const current = this.stack.pop();
+    if (!current) {
+      return false;
+    }
+    if (!current.isComplete) {
+      this.stack.push(current);
+      return false;
+    }
+    const parent = this.safeCurrent;
+
+    if (parent) {
+      if (parent.type === 'attribute' && fragments.isValue(current)) {
+        parent.value = current;
+        parent.isComplete = true;
+        return true;
+      }
+      if (parent.type === 'array' && fragments.isPrimitive(current)) {
+        parent.items.push(current);
+        return true;
+      }
+      if (
+        parent.type === 'record' &&
+        current.type === 'attribute' &&
+        current.value !== undefined
+      ) {
+        parent.children[current.name] = current.value;
+        return true;
+      }
+    } else if (current.type === 'record') {
+      this.results.push(current);
+      return true;
+    }
+
+    throw new CtxProcessingError(current, parent);
+  }
+
+  private processStack(): Ctx {
+    while (this.popStack()) {
+      // Nothing to do
+    }
+    return this;
+  }
+
+  addString(value: string): Ctx {
+    this.stack.push(fragments.string(value));
+    return this.processStack();
+  }
+
+  addBoolean(value: string | boolean): Ctx {
+    this.stack.push(fragments.boolean(value));
+    return this.processStack();
+  }
+
+  startRecord(children: ChildFragments = {}): Ctx {
+    const record = fragments.record(children);
+    this.stack.push(record);
+    return this;
+  }
+
+  endRecord(): Ctx {
+    const record = this.currentRecord;
+    record.isComplete = true;
+    return this.processStack();
+  }
+
+  startRule(name: string): Ctx {
+    return this.startRecord({ rule: fragments.string(name) });
+  }
+
+  endRule(): Ctx {
+    return this.endRecord();
+  }
+
+  startAttribute(name: string): Ctx {
+    this.stack.push(fragments.attribute(name));
+    return this.processStack();
+  }
+
+  startArray(): Ctx {
+    this.stack.push(fragments.array());
+    return this.processStack();
+  }
+
+  endArray(): Ctx {
+    const array = this.currentArray;
+    array.isComplete = true;
+    return this.processStack();
+  }
+}
diff --git a/lib/modules/manager/bazel-module/extract.spec.ts b/lib/modules/manager/bazel-module/extract.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8207d4e0f5dcf8fdf1b5d1e89577f3e8cc012569
--- /dev/null
+++ b/lib/modules/manager/bazel-module/extract.spec.ts
@@ -0,0 +1,58 @@
+import { codeBlock } from 'common-tags';
+import { BazelDatasource } from '../../datasource/bazel';
+import * as parser from './parser';
+import { extractPackageFile } from '.';
+
+describe('modules/manager/bazel-module/extract', () => {
+  describe('extractPackageFile()', () => {
+    it('returns null if fails to parse', () => {
+      const result = extractPackageFile('blahhhhh:foo:@what\n', 'MODULE.bazel');
+      expect(result).toBeNull();
+    });
+
+    it('returns null if something throws an error', () => {
+      jest.spyOn(parser, 'parse').mockImplementationOnce((input) => {
+        throw new Error('Test error');
+      });
+      const result = extractPackageFile('content', 'MODULE.bazel');
+      expect(result).toBeNull();
+    });
+
+    it('returns null if file is empty', () => {
+      const result = extractPackageFile('', 'MODULE.bazel');
+      expect(result).toBeNull();
+    });
+
+    it('returns null if file has not recognized declarations', () => {
+      const input = codeBlock`
+        ignore_me(name = "rules_foo", version = "1.2.3")
+      `;
+      const result = extractPackageFile(input, 'MODULE.bazel');
+      expect(result).toBeNull();
+    });
+
+    it('returns dependencies', () => {
+      const input = codeBlock`
+        bazel_dep(name = "rules_foo", version = "1.2.3")
+        bazel_dep(name = "rules_bar", version = "1.0.0", dev_dependency = True)
+      `;
+      const result = extractPackageFile(input, 'MODULE.bazel');
+      expect(result).toEqual({
+        deps: [
+          {
+            datasource: BazelDatasource.id,
+            depType: 'bazel_dep',
+            depName: 'rules_foo',
+            currentValue: '1.2.3',
+          },
+          {
+            datasource: BazelDatasource.id,
+            depType: 'bazel_dep',
+            depName: 'rules_bar',
+            currentValue: '1.0.0',
+          },
+        ],
+      });
+    });
+  });
+});
diff --git a/lib/modules/manager/bazel-module/extract.ts b/lib/modules/manager/bazel-module/extract.ts
new file mode 100644
index 0000000000000000000000000000000000000000..056900579d401ad1c0cd4d65ff351a0073151d97
--- /dev/null
+++ b/lib/modules/manager/bazel-module/extract.ts
@@ -0,0 +1,20 @@
+import { logger } from '../../../logger';
+import { LooseArray } from '../../../util/schema-utils';
+import type { PackageFileContent } from '../types';
+import { ToBazelDep } from './bazel-dep';
+import { parse } from './parser';
+
+export function extractPackageFile(
+  content: string,
+  filename: string
+): PackageFileContent | null {
+  try {
+    const records = parse(content);
+    return LooseArray(ToBazelDep)
+      .transform((deps) => (deps.length ? { deps } : null))
+      .parse(records);
+  } catch (err) {
+    logger.debug({ err, filename }, 'Failed to parse bazel module file.');
+    return null;
+  }
+}
diff --git a/lib/modules/manager/bazel-module/fragments.spec.ts b/lib/modules/manager/bazel-module/fragments.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bf30857ceef00f9708da1c2b9b3869b2590a51bf
--- /dev/null
+++ b/lib/modules/manager/bazel-module/fragments.spec.ts
@@ -0,0 +1,64 @@
+import {
+  ArrayFragmentSchema,
+  AttributeFragmentSchema,
+  BooleanFragmentSchema,
+  RecordFragmentSchema,
+  StringFragmentSchema,
+} from './fragments';
+import * as fragments from './fragments';
+
+describe('modules/manager/bazel-module/fragments', () => {
+  it('.string()', () => {
+    const result = fragments.string('hello');
+    expect(() => StringFragmentSchema.parse(result)).not.toThrow();
+    expect(result.value).toBe('hello');
+  });
+
+  it('.boolean()', () => {
+    const result = fragments.boolean(true);
+    expect(() => BooleanFragmentSchema.parse(result)).not.toThrow();
+    expect(result.value).toBe(true);
+  });
+
+  it('.record()', () => {
+    const result = fragments.record({ name: fragments.string('foo') }, true);
+    expect(() => RecordFragmentSchema.parse(result)).not.toThrow();
+    expect(result.children).toEqual({ name: fragments.string('foo') });
+    expect(result.isComplete).toBe(true);
+  });
+
+  it('.attribute()', () => {
+    const result = fragments.attribute('name', fragments.string('foo'), true);
+    expect(() => AttributeFragmentSchema.parse(result)).not.toThrow();
+    expect(result.name).toBe('name');
+    expect(result.value).toEqual(fragments.string('foo'));
+    expect(result.isComplete).toBe(true);
+  });
+
+  it('.array()', () => {
+    const result = fragments.array([fragments.string('foo')], true);
+    expect(() => ArrayFragmentSchema.parse(result)).not.toThrow();
+    expect(result.items).toEqual([fragments.string('foo')]);
+    expect(result.isComplete).toBe(true);
+  });
+
+  it.each`
+    a                            | exp
+    ${fragments.string('hello')} | ${true}
+    ${fragments.boolean(true)}   | ${true}
+    ${fragments.array()}         | ${true}
+    ${fragments.record()}        | ${false}
+  `('.isValue($a)', ({ a, exp }) => {
+    expect(fragments.isValue(a)).toBe(exp);
+  });
+
+  it.each`
+    a                            | exp
+    ${fragments.string('hello')} | ${true}
+    ${fragments.boolean(true)}   | ${true}
+    ${fragments.array()}         | ${false}
+    ${fragments.record()}        | ${false}
+  `('.isValue($a)', ({ a, exp }) => {
+    expect(fragments.isPrimitive(a)).toBe(exp);
+  });
+});
diff --git a/lib/modules/manager/bazel-module/fragments.ts b/lib/modules/manager/bazel-module/fragments.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0a873a4b2eed4ac998d92bf192404202bd7a20fc
--- /dev/null
+++ b/lib/modules/manager/bazel-module/fragments.ts
@@ -0,0 +1,117 @@
+import { z } from 'zod';
+import { LooseArray, LooseRecord } from '../../../util/schema-utils';
+import * as starlark from './starlark';
+
+export const StringFragmentSchema = z.object({
+  type: z.literal('string'),
+  value: z.string(),
+  isComplete: z.literal(true),
+});
+export const BooleanFragmentSchema = z.object({
+  type: z.literal('boolean'),
+  value: z.boolean(),
+  isComplete: z.literal(true),
+});
+const PrimitiveFragmentsSchema = z.discriminatedUnion('type', [
+  StringFragmentSchema,
+  BooleanFragmentSchema,
+]);
+export const ArrayFragmentSchema = z.object({
+  type: z.literal('array'),
+  items: LooseArray(PrimitiveFragmentsSchema),
+  isComplete: z.boolean(),
+});
+const ValueFragmentsSchema = z.discriminatedUnion('type', [
+  StringFragmentSchema,
+  BooleanFragmentSchema,
+  ArrayFragmentSchema,
+]);
+export const RecordFragmentSchema = z.object({
+  type: z.literal('record'),
+  children: LooseRecord(ValueFragmentsSchema),
+  isComplete: z.boolean(),
+});
+export const AttributeFragmentSchema = z.object({
+  type: z.literal('attribute'),
+  name: z.string(),
+  value: ValueFragmentsSchema.optional(),
+  isComplete: z.boolean(),
+});
+const AllFragmentsSchema = z.discriminatedUnion('type', [
+  ArrayFragmentSchema,
+  AttributeFragmentSchema,
+  BooleanFragmentSchema,
+  RecordFragmentSchema,
+  StringFragmentSchema,
+]);
+
+export type AllFragments = z.infer<typeof AllFragmentsSchema>;
+export type ArrayFragment = z.infer<typeof ArrayFragmentSchema>;
+export type AttributeFragment = z.infer<typeof AttributeFragmentSchema>;
+export type BooleanFragment = z.infer<typeof BooleanFragmentSchema>;
+export type ChildFragments = Record<string, ValueFragments>;
+export type PrimitiveFragments = z.infer<typeof PrimitiveFragmentsSchema>;
+export type RecordFragment = z.infer<typeof RecordFragmentSchema>;
+export type StringFragment = z.infer<typeof StringFragmentSchema>;
+export type ValueFragments = z.infer<typeof ValueFragmentsSchema>;
+
+export function string(value: string): StringFragment {
+  return {
+    type: 'string',
+    isComplete: true,
+    value,
+  };
+}
+
+export function boolean(value: string | boolean): BooleanFragment {
+  return {
+    type: 'boolean',
+    isComplete: true,
+    value: typeof value === 'string' ? starlark.asBoolean(value) : value,
+  };
+}
+
+export function record(
+  children: ChildFragments = {},
+  isComplete = false
+): RecordFragment {
+  return {
+    type: 'record',
+    isComplete,
+    children,
+  };
+}
+
+export function attribute(
+  name: string,
+  value?: ValueFragments,
+  isComplete = false
+): AttributeFragment {
+  return {
+    type: 'attribute',
+    name,
+    value,
+    isComplete,
+  };
+}
+
+export function array(
+  items: PrimitiveFragments[] = [],
+  isComplete = false
+): ArrayFragment {
+  return {
+    type: 'array',
+    items,
+    isComplete,
+  };
+}
+
+export function isValue(data: unknown): data is ValueFragments {
+  const result = ValueFragmentsSchema.safeParse(data);
+  return result.success;
+}
+
+export function isPrimitive(data: unknown): data is PrimitiveFragments {
+  const result = PrimitiveFragmentsSchema.safeParse(data);
+  return result.success;
+}
diff --git a/lib/modules/manager/bazel-module/index.ts b/lib/modules/manager/bazel-module/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3312b93fe6573d13060d1ce63b5a0ed5270407cf
--- /dev/null
+++ b/lib/modules/manager/bazel-module/index.ts
@@ -0,0 +1,14 @@
+import { BazelDatasource } from '../../datasource/bazel';
+import { extractPackageFile } from './extract';
+
+export { extractPackageFile };
+
+export const defaultConfig = {
+  fileMatch: ['(^|/)MODULE\\.bazel$'],
+  // The bazel-module manager is still under development. The milestone
+  // tracking the release of this manager is at
+  // https://github.com/renovatebot/renovate/issues/13658.
+  enabled: false,
+};
+
+export const supportedDatasources = [BazelDatasource.id];
diff --git a/lib/modules/manager/bazel-module/parser.spec.ts b/lib/modules/manager/bazel-module/parser.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..56392fef8a50ec8a5e650c2b125017b94e61182d
--- /dev/null
+++ b/lib/modules/manager/bazel-module/parser.spec.ts
@@ -0,0 +1,44 @@
+import { codeBlock } from 'common-tags';
+import * as fragments from './fragments';
+import { parse } from './parser';
+
+describe('modules/manager/bazel-module/parser', () => {
+  describe('parse', () => {
+    it('returns empty string if invalid content', () => {
+      const input = codeBlock`
+      // This is invalid
+      a + 1
+      <<<<<<<
+      `;
+      const res = parse(input);
+      expect(res).toHaveLength(0);
+    });
+
+    it('finds simple bazel_dep', () => {
+      const input = codeBlock`
+        bazel_dep(name = "rules_foo", version = "1.2.3")
+        bazel_dep(name = "rules_bar", version = "1.0.0", dev_dependency = True)
+      `;
+      const res = parse(input);
+      expect(res).toEqual([
+        fragments.record(
+          {
+            rule: fragments.string('bazel_dep'),
+            name: fragments.string('rules_foo'),
+            version: fragments.string('1.2.3'),
+          },
+          true
+        ),
+        fragments.record(
+          {
+            rule: fragments.string('bazel_dep'),
+            name: fragments.string('rules_bar'),
+            version: fragments.string('1.0.0'),
+            dev_dependency: fragments.boolean(true),
+          },
+          true
+        ),
+      ]);
+    });
+  });
+});
diff --git a/lib/modules/manager/bazel-module/parser.ts b/lib/modules/manager/bazel-module/parser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6a33772183e94224017bfd7888ad3369e8012774
--- /dev/null
+++ b/lib/modules/manager/bazel-module/parser.ts
@@ -0,0 +1,48 @@
+import { lang, query as q } from 'good-enough-parser';
+import { regEx } from '../../../util/regex';
+import { Ctx } from './context';
+import type { RecordFragment } from './fragments';
+import * as starlark from './starlark';
+
+const booleanValuesRegex = regEx(`^${starlark.booleanStringValues.join('|')}$`);
+const supportedRules = ['bazel_dep'];
+const supportedRulesRegex = regEx(`^${supportedRules.join('|')}$`);
+
+/**
+ * Matches key-value pairs:
+ * - `name = "foobar"`
+ * - `dev_dependeny = True`
+ **/
+const kvParams = q
+  .sym<Ctx>((ctx, token) => ctx.startAttribute(token.value))
+  .op('=')
+  .alt(
+    q.str((ctx, token) => ctx.addString(token.value)),
+    q.sym<Ctx>(booleanValuesRegex, (ctx, token) => ctx.addBoolean(token.value))
+  );
+
+const moduleRules = q
+  .sym<Ctx>(supportedRulesRegex, (ctx, token) => ctx.startRule(token.value))
+  .join(
+    q.tree({
+      type: 'wrapped-tree',
+      maxDepth: 1,
+      search: kvParams,
+      postHandler: (ctx, tree) => ctx.endRule(),
+    })
+  );
+
+const rule = q.alt<Ctx>(moduleRules);
+
+const query = q.tree<Ctx>({
+  type: 'root-tree',
+  maxDepth: 16,
+  search: rule,
+});
+
+const starlarkLang = lang.createLang('starlark');
+
+export function parse(input: string): RecordFragment[] {
+  const parsedResult = starlarkLang.query(input, query, new Ctx());
+  return parsedResult?.results ?? [];
+}
diff --git a/lib/modules/manager/bazel-module/readme.md b/lib/modules/manager/bazel-module/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..06ce659dcaf89f183f6d95512509b3cd12bf3ed4
--- /dev/null
+++ b/lib/modules/manager/bazel-module/readme.md
@@ -0,0 +1,8 @@
+<!-- prettier-ignore -->
+!!! warning
+    The `bazel-module` manager is a work-in-progress.
+    It is currently disabled.
+    The manager only supports updating `bazel_dep` declarations.
+    For more information, see [issue 13658](https://github.com/renovatebot/renovate/issues/13658).
+
+The `bazel-module` manager can update [Bazel module (bzlmod)](https://bazel.build/external/module) enabled workspaces.
diff --git a/lib/modules/manager/bazel-module/starlark.spec.ts b/lib/modules/manager/bazel-module/starlark.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..efd3e50ab19a74310080b7da4e3043b6e6c84fcb
--- /dev/null
+++ b/lib/modules/manager/bazel-module/starlark.spec.ts
@@ -0,0 +1,17 @@
+import * as starlark from './starlark';
+
+describe('modules/manager/bazel-module/starlark', () => {
+  it.each`
+    a          | exp
+    ${'True'}  | ${true}
+    ${'False'} | ${false}
+  `('.asBoolean($a)', ({ a, exp }) => {
+    expect(starlark.asBoolean(a)).toBe(exp);
+  });
+
+  it('asBoolean', () => {
+    expect(() => starlark.asBoolean('bad')).toThrow(
+      new Error('Invalid Starlark boolean string: bad')
+    );
+  });
+});
diff --git a/lib/modules/manager/bazel-module/starlark.ts b/lib/modules/manager/bazel-module/starlark.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7fb06d2db338bb3b8b822a1a7357b9e50a3e73b5
--- /dev/null
+++ b/lib/modules/manager/bazel-module/starlark.ts
@@ -0,0 +1,16 @@
+import is from '@sindresorhus/is';
+
+const stringMapping: ReadonlyMap<string, boolean> = new Map<string, boolean>([
+  ['True', true],
+  ['False', false],
+]);
+
+export const booleanStringValues = Array.from(stringMapping.keys());
+
+export function asBoolean(value: string): boolean {
+  const result = stringMapping.get(value);
+  if (is.boolean(result)) {
+    return result;
+  }
+  throw new Error(`Invalid Starlark boolean string: ${value}`);
+}