diff --git a/.changeset/mighty-rabbits-teach.md b/.changeset/mighty-rabbits-teach.md
new file mode 100644
index 0000000000..0d80ddd51a
--- /dev/null
+++ b/.changeset/mighty-rabbits-teach.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: warn when using rest or identifier in custom elements without props option
diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md
index b579d38602..2af9021a6a 100644
--- a/documentation/docs/98-reference/.generated/compile-warnings.md
+++ b/documentation/docs/98-reference/.generated/compile-warnings.md
@@ -632,6 +632,12 @@ In some situations a selector may target an element that is not 'visible' to the
```
+### custom_element_props_identifier
+
+```
+Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.
+```
+
### element_implicitly_closed
```
diff --git a/packages/svelte/messages/compile-warnings/script.md b/packages/svelte/messages/compile-warnings/script.md
index 6603759156..26bd0c9027 100644
--- a/packages/svelte/messages/compile-warnings/script.md
+++ b/packages/svelte/messages/compile-warnings/script.md
@@ -1,3 +1,7 @@
+## custom_element_props_identifier
+
+> Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.
+
## export_let_unused
> Component has unused export property '%name%'. If it is for external reference only, please consider using `export const %name%`
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
index a7d08d315d..c4b4272aba 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
@@ -4,6 +4,7 @@
import { get_rune } from '../../scope.js';
import { ensure_no_module_import_conflict, validate_identifier_name } from './shared/utils.js';
import * as e from '../../../errors.js';
+import * as w from '../../../warnings.js';
import { extract_paths } from '../../../utils/ast.js';
import { equal } from '../../../utils/assert.js';
@@ -52,6 +53,19 @@ export function VariableDeclarator(node, context) {
e.props_invalid_identifier(node);
}
+ if (
+ context.state.analysis.custom_element &&
+ context.state.options.customElementOptions?.props == null
+ ) {
+ let warn_on;
+ if (
+ node.id.type === 'Identifier' ||
+ (warn_on = node.id.properties.find((p) => p.type === 'RestElement')) != null
+ ) {
+ w.custom_element_props_identifier(warn_on ?? node.id);
+ }
+ }
+
context.state.analysis.needs_props = true;
if (node.id.type === 'Identifier') {
diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js
index 2a8316308c..1226190891 100644
--- a/packages/svelte/src/compiler/warnings.js
+++ b/packages/svelte/src/compiler/warnings.js
@@ -96,6 +96,7 @@ export const codes = [
'options_removed_hydratable',
'options_removed_loop_guard_timeout',
'options_renamed_ssr_dom',
+ 'custom_element_props_identifier',
'export_let_unused',
'legacy_component_creation',
'non_reactive_update',
@@ -592,6 +593,14 @@ export function options_renamed_ssr_dom(node) {
w(node, 'options_renamed_ssr_dom', `\`generate: "dom"\` and \`generate: "ssr"\` options have been renamed to "client" and "server" respectively\nhttps://svelte.dev/e/options_renamed_ssr_dom`);
}
+/**
+ * Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.
+ * @param {null | NodeLike} node
+ */
+export function custom_element_props_identifier(node) {
+ w(node, 'custom_element_props_identifier', `Using a rest element or a non-destructured declaration with \`$props()\` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the \`customElement.props\` option.\nhttps://svelte.dev/e/custom_element_props_identifier`);
+}
+
/**
* Component has unused export property '%name%'. If it is for external reference only, please consider using `export const %name%`
* @param {null | NodeLike} node
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte
new file mode 100644
index 0000000000..bb7b930dc3
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/input.svelte
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json
new file mode 100644
index 0000000000..b880fe146c
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-props-option/warnings.json
@@ -0,0 +1,14 @@
+[
+ {
+ "code": "options_missing_custom_element",
+ "end": {
+ "column": 2,
+ "line": 3
+ },
+ "message": "The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?",
+ "start": {
+ "column": 16,
+ "line": 1
+ }
+ }
+]
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte
new file mode 100644
index 0000000000..207b554527
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/input.svelte
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json
new file mode 100644
index 0000000000..61e11ab108
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier-rest/warnings.json
@@ -0,0 +1,26 @@
+[
+ {
+ "code": "options_missing_custom_element",
+ "end": {
+ "column": 34,
+ "line": 1
+ },
+ "message": "The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?",
+ "start": {
+ "column": 16,
+ "line": 1
+ }
+ },
+ {
+ "code": "custom_element_props_identifier",
+ "end": {
+ "column": 15,
+ "line": 4
+ },
+ "message": "Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.",
+ "start": {
+ "column": 7,
+ "line": 4
+ }
+ }
+]
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte b/packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte
new file mode 100644
index 0000000000..ca5b16f8c3
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier/input.svelte
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json b/packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json
new file mode 100644
index 0000000000..4c50bbd116
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/custom-element-props-identifier/warnings.json
@@ -0,0 +1,26 @@
+[
+ {
+ "code": "options_missing_custom_element",
+ "end": {
+ "column": 34,
+ "line": 1
+ },
+ "message": "The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?",
+ "start": {
+ "column": 16,
+ "line": 1
+ }
+ },
+ {
+ "code": "custom_element_props_identifier",
+ "end": {
+ "column": 10,
+ "line": 4
+ },
+ "message": "Using a rest element or a non-destructured declaration with `$props()` means that Svelte can't infer what properties to expose when creating a custom element. Consider destructuring all the props or explicitly specifying the `customElement.props` option.",
+ "start": {
+ "column": 5,
+ "line": 4
+ }
+ }
+]