diff --git a/docs/rules/README.md b/docs/rules/README.md
index fe113336e..d6413bba2 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -301,6 +301,7 @@ For example:
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | |
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
| [vue/no-restricted-component-options](./no-restricted-component-options.md) | disallow specific component option | |
+| [vue/no-restricted-props](./no-restricted-props.md) | disallow specific props | |
| [vue/no-restricted-static-attribute](./no-restricted-static-attribute.md) | disallow specific attribute | |
| [vue/no-restricted-v-bind](./no-restricted-v-bind.md) | disallow specific argument in `v-bind` | |
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | |
diff --git a/docs/rules/no-restricted-props.md b/docs/rules/no-restricted-props.md
new file mode 100644
index 000000000..664b6f2cd
--- /dev/null
+++ b/docs/rules/no-restricted-props.md
@@ -0,0 +1,104 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-restricted-props
+description: disallow specific props
+---
+# vue/no-restricted-props
+> disallow specific props
+
+## :book: Rule Details
+
+This rule allows you to specify props that you don't want to use in your application.
+
+## :wrench: Options
+
+This rule takes a list of strings, where each string is a prop name or pattern to be restricted:
+
+```json
+{
+ "vue/no-restricted-props": ["error", "value", "/^forbidden/"]
+}
+```
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+Alternatively, the rule also accepts objects.
+
+```json
+{
+ "vue/no-restricted-props": ["error",
+ {
+ "name": "value",
+ "message": "If you intend a prop for v-model, it should be 'modelValue' in Vue 3.",
+ "suggest": "modelValue"
+ },
+ ]
+}
+```
+
+The following properties can be specified for the object.
+
+- `name` ... Specify the prop name or pattern.
+- `message` ... Specify an optional custom message.
+- `suggest` ... Specify an optional name to suggest changes.
+
+
+
+```vue
+
+```
+
+
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-restricted-props.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-restricted-props.js)
diff --git a/lib/index.js b/lib/index.js
index 7755e9f1e..abaac1087 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -91,6 +91,7 @@ module.exports = {
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
'no-reserved-keys': require('./rules/no-reserved-keys'),
'no-restricted-component-options': require('./rules/no-restricted-component-options'),
+ 'no-restricted-props': require('./rules/no-restricted-props'),
'no-restricted-static-attribute': require('./rules/no-restricted-static-attribute'),
'no-restricted-syntax': require('./rules/no-restricted-syntax'),
'no-restricted-v-bind': require('./rules/no-restricted-v-bind'),
diff --git a/lib/rules/no-restricted-props.js b/lib/rules/no-restricted-props.js
new file mode 100644
index 000000000..d1ba3bc49
--- /dev/null
+++ b/lib/rules/no-restricted-props.js
@@ -0,0 +1,149 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const regexp = require('../utils/regexp')
+
+/**
+ * @typedef {object} ParsedOption
+ * @property { (name: string) => boolean } test
+ * @property {string} [message]
+ * @property {string} [suggest]
+ */
+
+/**
+ * @param {string} str
+ * @returns {(str: string) => boolean}
+ */
+function buildMatcher(str) {
+ if (regexp.isRegExp(str)) {
+ const re = regexp.toRegExp(str)
+ return (s) => {
+ re.lastIndex = 0
+ return re.test(s)
+ }
+ }
+ return (s) => s === str
+}
+/**
+ * @param {string|{name:string, message?: string, suggest?:string}} option
+ * @returns {ParsedOption}
+ */
+function parseOption(option) {
+ if (typeof option === 'string') {
+ const matcher = buildMatcher(option)
+ return {
+ test(name) {
+ return matcher(name)
+ }
+ }
+ }
+ const parsed = parseOption(option.name)
+ parsed.message = option.message
+ parsed.suggest = option.suggest
+ return parsed
+}
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow specific props',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-restricted-props.html'
+ },
+ fixable: null,
+ schema: {
+ type: 'array',
+ items: {
+ oneOf: [
+ { type: ['string'] },
+ {
+ type: 'object',
+ properties: {
+ name: { type: 'string' },
+ message: { type: 'string', minLength: 1 },
+ suggest: { type: 'string' }
+ },
+ required: ['name'],
+ additionalProperties: false
+ }
+ ]
+ },
+ uniqueItems: true,
+ minItems: 0
+ },
+
+ messages: {
+ // eslint-disable-next-line eslint-plugin/report-message-format
+ restrictedProp: '{{message}}',
+ instead: 'Instead, change to `{{suggest}}`.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @type {ParsedOption[]} */
+ const options = context.options.map(parseOption)
+
+ return utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ for (const prop of utils.getComponentProps(node)) {
+ if (!prop.propName) {
+ continue
+ }
+
+ for (const option of options) {
+ if (option.test(prop.propName)) {
+ const message =
+ option.message ||
+ `Using \`${prop.propName}\` props is not allowed.`
+ context.report({
+ node: prop.key,
+ messageId: 'restrictedProp',
+ data: { message },
+ suggest: createSuggest(prop.key, option)
+ })
+ break
+ }
+ }
+ }
+ }
+ })
+ }
+}
+
+/**
+ * @param {Expression} node
+ * @param {ParsedOption} option
+ * @returns {Rule.SuggestionReportDescriptor[]}
+ */
+function createSuggest(node, option) {
+ if (!option.suggest) {
+ return []
+ }
+
+ /** @type {string} */
+ let replaceText
+ if (node.type === 'Literal' || node.type === 'TemplateLiteral') {
+ replaceText = JSON.stringify(option.suggest)
+ } else if (node.type === 'Identifier') {
+ replaceText = /^[a-z]\w*$/iu.exec(option.suggest)
+ ? option.suggest
+ : JSON.stringify(option.suggest)
+ } else {
+ return []
+ }
+
+ return [
+ {
+ fix(fixer) {
+ return fixer.replaceText(node, replaceText)
+ },
+ messageId: 'instead',
+ data: { suggest: option.suggest }
+ }
+ ]
+}
diff --git a/tests/lib/rules/no-restricted-props.js b/tests/lib/rules/no-restricted-props.js
new file mode 100644
index 000000000..4206b4014
--- /dev/null
+++ b/tests/lib/rules/no-restricted-props.js
@@ -0,0 +1,331 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-restricted-props')
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
+})
+
+tester.run('no-restricted-props', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['foo', 'bar', 'baz']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['foo', 'bar', 'baz']
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['bad'],
+ errors: [
+ {
+ message: 'Using `bad` props is not allowed.',
+ line: 6,
+ column: 11
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['bad'],
+ errors: [
+ {
+ message: 'Using `bad` props is not allowed.',
+ line: 6
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['/a/'],
+ errors: ['Using `bad` props is not allowed.']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [{ name: 'bar' }, { name: 'baz', message: 'Using Baz' }],
+ errors: [
+ {
+ message: 'Using `bar` props is not allowed.',
+ line: 6
+ },
+ {
+ message: 'Using Baz',
+ line: 7
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [
+ { name: 'foo', suggest: 'Foo' },
+ { name: 'bar', suggest: 'Bar' },
+ { name: '0', suggest: 'Zero' }
+ ],
+ errors: [
+ {
+ message: 'Using `foo` props is not allowed.',
+ line: 5,
+ suggestions: [
+ {
+ desc: 'Instead, change to `Foo`.',
+ output: `
+
+ `
+ }
+ ]
+ },
+ {
+ message: 'Using `bar` props is not allowed.',
+ line: 6,
+ suggestions: [
+ {
+ desc: 'Instead, change to `Bar`.',
+ output: `
+
+ `
+ }
+ ]
+ },
+ {
+ message: 'Using `0` props is not allowed.',
+ line: 7,
+ suggestions: [
+ {
+ desc: 'Instead, change to `Zero`.',
+ output: `
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [
+ { name: 'foo', suggest: 'Foo' },
+ { name: 'bar', suggest: 'b-a-r' },
+ { name: 'baz', suggest: 'Baz' }
+ ],
+ errors: [
+ {
+ message: 'Using `foo` props is not allowed.',
+ line: 5,
+ suggestions: [
+ {
+ desc: 'Instead, change to `Foo`.',
+ output: `
+
+ `
+ }
+ ]
+ },
+ {
+ message: 'Using `bar` props is not allowed.',
+ line: 6,
+ suggestions: [
+ {
+ desc: 'Instead, change to `b-a-r`.',
+ output: `
+
+ `
+ }
+ ]
+ },
+ {
+ message: 'Using `baz` props is not allowed.',
+ line: 7,
+ suggestions: [
+ {
+ desc: 'Instead, change to `Baz`.',
+ output: `
+
+ `
+ }
+ ]
+ }
+ ]
+ }
+ ]
+})