diff --git a/README.md b/README.md
index 4afd8f124e..7829714bad 100644
--- a/README.md
+++ b/README.md
@@ -414,6 +414,7 @@ rules in templates can be disabled with eslint directives with mustache or html
| [template-linebreak-style](docs/rules/template-linebreak-style.md) | enforce consistent linebreaks in templates | | 🔧 | |
| [template-modifier-name-case](docs/rules/template-modifier-name-case.md) | require dasherized names for modifiers | | 🔧 | |
| [template-no-only-default-slot](docs/rules/template-no-only-default-slot.md) | disallow using only the default slot | | 🔧 | |
+| [template-template-length](docs/rules/template-template-length.md) | enforce template size constraints | | | |
### Testing
diff --git a/docs/rules/template-template-length.md b/docs/rules/template-template-length.md
new file mode 100644
index 0000000000..0900eb784b
--- /dev/null
+++ b/docs/rules/template-template-length.md
@@ -0,0 +1,57 @@
+# ember/template-template-length
+
+
+
+Enforce template size constraints.
+
+Very long templates can indicate that markup should be split into smaller components.
+Very short templates can indicate that markup might be better inlined.
+
+## Config
+
+This rule accepts either:
+
+- `true` / `false`
+- an object with:
+ - `max` (integer): maximum allowed template length in lines
+ - `min` (integer): minimum allowed template length in lines
+
+Using `true` defaults to:
+
+- `max: 200`
+- `min: 5`
+
+## Examples
+
+Examples of **incorrect** code for this rule:
+
+```gjs
+
+ short
+
+```
+
+with config:
+
+```json
+{ "min": 10 }
+```
+
+Examples of **correct** code for this rule:
+
+```gjs
+
+ line 1
+ line 2
+ line 3
+
+```
+
+## Related rules
+
+- [eslint/max-lines](https://eslint.org/docs/rules/max-lines)
+
+## References
+
+- [eslint-plugin-ember template-length](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-template-length.md)
+- [eslint/max-lines](https://eslint.org/docs/latest/rules/max-lines)
diff --git a/lib/rules/template-template-length.js b/lib/rules/template-template-length.js
new file mode 100644
index 0000000000..aa14ba729b
--- /dev/null
+++ b/lib/rules/template-template-length.js
@@ -0,0 +1,119 @@
+const DEFAULT_MAX_LENGTH = 200;
+const DEFAULT_MIN_LENGTH = 5;
+
+const DEFAULT_CONFIG = {
+ max: DEFAULT_MAX_LENGTH,
+ min: DEFAULT_MIN_LENGTH,
+};
+
+function isValidConfigObjectFormat(config) {
+ for (const key of Object.keys(config)) {
+ const value = config[key];
+
+ if (key !== 'min' && key !== 'max') {
+ return false;
+ }
+
+ if (!Number.isInteger(value)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function parseConfig(config) {
+ const configType = typeof config;
+
+ switch (configType) {
+ case 'boolean': {
+ return config ? DEFAULT_CONFIG : {};
+ }
+ case 'object': {
+ if (config === null) {
+ break;
+ }
+
+ if (isValidConfigObjectFormat(config)) {
+ return config;
+ }
+ break;
+ }
+ case 'undefined': {
+ return {};
+ }
+ // No default
+ }
+
+ throw new Error(
+ 'The ember/template-template-length rule accepts: boolean, or object with integer "min" and/or "max" keys.'
+ );
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce template size constraints',
+ category: 'Stylistic Issues',
+ recommendedGjs: false,
+ recommendedGts: false,
+ url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-template-length.md',
+ templateMode: 'both',
+ },
+ schema: [
+ {
+ oneOf: [
+ { type: 'boolean' },
+ {
+ type: 'object',
+ properties: {
+ min: { type: 'integer' },
+ max: { type: 'integer' },
+ },
+ additionalProperties: false,
+ },
+ ],
+ },
+ ],
+ messages: {
+ tooLong: 'Template length of {{length}} exceeds {{max}}',
+ tooShort: 'Template length of {{length}} is smaller than {{min}}',
+ },
+ originallyFrom: {
+ name: 'ember-template-lint',
+ rule: 'lib/rules/template-length.js',
+ docs: 'docs/rule/template-length.md',
+ tests: 'test/unit/rules/template-length-test.js',
+ },
+ },
+
+ create(context) {
+ const config = parseConfig(context.options[0]);
+
+ if (!config.min && !config.max) {
+ return {};
+ }
+
+ return {
+ 'GlimmerTemplate:exit'(node) {
+ const length = node.loc.end.line - node.loc.start.line + 1;
+
+ if (config.max && length > config.max) {
+ context.report({
+ node,
+ messageId: 'tooLong',
+ data: { length, max: config.max },
+ });
+ } else if (config.min && length < config.min) {
+ context.report({
+ node,
+ messageId: 'tooShort',
+ data: { length, min: config.min },
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tests/lib/rules/template-template-length.js b/tests/lib/rules/template-template-length.js
new file mode 100644
index 0000000000..c9faaed09e
--- /dev/null
+++ b/tests/lib/rules/template-template-length.js
@@ -0,0 +1,116 @@
+const rule = require('../../../lib/rules/template-template-length');
+const RuleTester = require('eslint').RuleTester;
+
+const ruleTester = new RuleTester({
+ parser: require.resolve('ember-eslint-parser'),
+ parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
+});
+
+ruleTester.run('template-template-length', rule, {
+ valid: [
+ {
+ code: `
+ one
+ two
+`,
+ options: [{ max: 5 }],
+ },
+ {
+ code: `
+ one
+ two
+ three
+`,
+ options: [{ min: 3 }],
+ },
+ {
+ code: `
+ one
+`,
+ options: [false],
+ },
+
+ `testing this
+and
+this
+and his`,
+ ],
+ invalid: [
+ {
+ code: `
+ one
+ two
+`,
+ output: null,
+ options: [{ min: 10 }],
+ errors: [{ message: 'Template length of 4 is smaller than 10' }],
+ },
+ {
+ code: `
+ one
+ two
+ three
+`,
+ output: null,
+ options: [{ max: 3 }],
+ errors: [{ message: 'Template length of 5 exceeds 3' }],
+ },
+ ],
+});
+
+const hbsRuleTester = new RuleTester({
+ parser: require.resolve('ember-eslint-parser/hbs'),
+ parserOptions: {
+ ecmaVersion: 2022,
+ sourceType: 'module',
+ },
+});
+
+hbsRuleTester.run('template-template-length', rule, {
+ valid: [
+ `testing this
+and
+this
+and his`,
+ `testing
+this
+`,
+ `testing
+this
+and his
+`,
+ `testing
+this
+andthis
+`,
+ // Config: max option
+ {
+ code: 'testing\nthis\n',
+ options: [{ max: 10 }],
+ },
+ // Config: min option
+ {
+ code: 'testing\nthis\nand\this\n',
+ options: [{ min: 1 }],
+ },
+ // Config: min + max options
+ {
+ code: 'testing\nthis\nandthis\n',
+ options: [{ min: 1, max: 5 }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'testing\ntoo-short template\n',
+ output: null,
+ options: [{ min: 10 }],
+ errors: [{ message: 'Template length of 3 is smaller than 10' }],
+ },
+ {
+ code: 'test\nthis\nand\nthis\n',
+ output: null,
+ options: [{ max: 3 }],
+ errors: [{ message: 'Template length of 5 exceeds 3' }],
+ },
+ ],
+});