diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h
index bafca5fb6bc9d558dc5e475d512431260210403f..3a0e1b81a5c75d6e99c8bc3294e89c0f003d91ed 100644
--- a/include/asterisk/sorcery.h
+++ b/include/asterisk/sorcery.h
@@ -561,6 +561,169 @@ enum ast_sorcery_apply_result __ast_sorcery_insert_wizard_mapping(struct ast_sor
 	__ast_sorcery_insert_wizard_mapping((sorcery), (type), AST_MODULE, (name), (data), \
 		(caching), (position))
 
+/*!
+ * \brief Wizard Apply Flags
+ *
+ * These flags apply only to a wizard/object-type combination.
+ * The same wizard may be applied to a different object-type with
+ * different flags and behavior.  If ALLOW_DUPLICATE is set
+ * the wizard could even be applied to the same object-type
+ * with different flags.
+ */
+enum ast_sorcery_wizard_apply_flags {
+	/*! Apply no special behavior */
+	AST_SORCERY_WIZARD_APPLY_NONE = (0 << 0),
+	/*! This wizard will cache this object type's entries */
+	AST_SORCERY_WIZARD_APPLY_CACHING = (1 << 0),
+	/*! This wizard won't allow Create, Update or Delete operations on this object type */
+	AST_SORCERY_WIZARD_APPLY_READONLY = (1 << 1),
+	/*! This wizard will allow other instances of itself on the same object type */
+	AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE = (1 << 2)
+};
+
+/*!
+ * \brief Insert an additional object wizard mapping at a specific position
+ * in the wizard list returning wizard information
+ * \since 13.26.0
+ * \since 16.3.0
+ *
+ * \param sorcery Pointer to a sorcery structure
+ * \param object_type_name Name of the object type to apply to
+ * \param module The name of the module, typically AST_MODULE
+ * \param wizard_type_name Name of the wizard type to use
+ * \param wizard_args Opaque string to be passed to the wizard
+ *             May be NULL but see note below
+ * \param flags One or more of enum ast_sorcery_wizard_apply_flags
+ * \param position An index to insert to or one of ast_sorcery_wizard_position
+ * \param[out] wizard A variable to receive a pointer to the ast_sorcery_wizard structure.
+ *             May be NULL if not needed.
+ * \param[out] wizard_data A variable to receive a pointer to the wizard's internal data.
+ *             May be NULL if not needed.
+ *
+ * \return What occurred when applying the mapping
+ *
+ * \note This should be called *after* applying default mappings
+ *
+ * \note Although \ref wizard_args is an optional parameter it is highly
+ * recommended to supply one.  If you use the AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE
+ * flag, and you intend to ever remove a wizard mapping, you'll need wizard_args
+ * to remove specific instances of a wizard type.
+ */
+enum ast_sorcery_apply_result __ast_sorcery_object_type_insert_wizard(struct ast_sorcery *sorcery,
+	const char *object_type_name, const char *module, const char *wizard_type_name,
+	const char *wizard_args, enum ast_sorcery_wizard_apply_flags flags, int position,
+	struct ast_sorcery_wizard **wizard, void **wizard_data);
+
+/*!
+ * \brief Insert an additional object wizard mapping at a specific position
+ * in the wizard list returning wizard information
+ * \since 13.26.0
+ * \since 16.3.0
+ *
+ * \param sorcery Pointer to a sorcery structure
+ * \param object_type_name Name of the object type to apply to
+ * \param wizard_type_name Name of the wizard type to use
+ * \param wizard_args Opaque string to be passed to the wizard
+ *             May be NULL but see note below
+ * \param flags One or more of enum ast_sorcery_wizard_apply_flags
+ * \param position An index to insert to or one of ast_sorcery_wizard_position
+ * \param[out] wizard A variable to receive a pointer to the ast_sorcery_wizard structure.
+ *             May be NULL if not needed.
+ * \param[out] wizard_data A variable to receive a pointer to the wizard's internal data.
+ *             May be NULL if not needed.
+ *
+ * \return What occurred when applying the mapping
+ *
+ * \note This should be called *after* applying default mappings
+ *
+ * \note Although \ref wizard_args is an optional parameter it is highly
+ * recommended to supply one.  If you use the AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE
+ * flag, and you intend to ever remove a wizard mapping, you'll need wizard_args
+ * to remove specific instances.
+ */
+#define ast_sorcery_object_type_insert_wizard(sorcery, \
+	object_type_name, wizard_type_name, wizard_args, flags, \
+	position, wizard, wizard_data) \
+	__ast_sorcery_object_type_insert_wizard((sorcery), \
+		(object_type_name), AST_MODULE, (wizard_type_name), (wizard_args), (flags), \
+		position, (wizard), (wizard_data))
+
+/*!
+ * \brief Apply additional object wizard mappings returning wizard information
+ * \since 13.26.0
+ * \since 16.3.0
+ *
+ * \param sorcery Pointer to a sorcery structure
+ * \param object_type_name Name of the object type to apply to
+ * \param wizard_type_name Name of the wizard type to use
+ * \param wizard_args Opaque string to be passed to the wizard
+ *             May be NULL but see note below
+ * \param flags One or more of enum ast_sorcery_wizard_apply_flags
+ * \param[out] wizard A variable to receive a pointer to the ast_sorcery_wizard structure.
+ *             May be NULL if not needed.
+ * \param[out] wizard_data A variable to receive a pointer to the wizard's internal data.
+ *             May be NULL if not needed.
+ *
+ * \return What occurred when applying the mapping
+ *
+ * \note This should be called *after* applying default mappings
+ *
+ * \note Although \ref wizard_args is an optional parameter it is highly
+ * recommended to supply one.  If you use the AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE
+ * flag, and you intend to ever remove a wizard mapping, you'll need wizard_args
+ * to remove specific instances.
+ */
+#define ast_sorcery_object_type_apply_wizard(sorcery, \
+	object_type_name, wizard_type_name, wizard_args, flags, \
+	wizard, wizard_data) \
+	__ast_sorcery_object_type_insert_wizard((sorcery), \
+		(object_type_name), AST_MODULE, (wizard_type_name), (wizard_args), (flags), \
+		AST_SORCERY_WIZARD_POSITION_LAST, (wizard), (wizard_data))
+
+/*!
+ * \brief Remove an object wizard mapping
+ * \since 13.26.0
+ * \since 16.3.0
+ *
+ * \param sorcery Pointer to a sorcery structure
+ * \param object_type_name Name of the object type to remove from
+ * \param module The name of the module, typically AST_MODULE
+ * \param wizard_type_name The name of the of the wizard type to remove
+ * \param wizard_args Opaque string originally passed to the wizard
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note If there were multiple instances of the same wizard type
+ * added to this object type without using \ref wizard_args, then
+ * only the first wizard matching wizard_type will be removed.
+ */
+int __ast_sorcery_object_type_remove_wizard(struct ast_sorcery *sorcery,
+	const char *object_type_name, const char *module, const char *wizard_type_name,
+	const char *wizard_args);
+
+/*!
+ * \brief Remove an object wizard mapping
+ * \since 13.26.0
+ * \since 16.3.0
+ *
+ * \param sorcery Pointer to a sorcery structure
+ * \param object_type_name Name of the object type to remove from
+ * \param wizard_type_name The name of the of the wizard type to remove
+ * \param wizard_args Opaque string originally passed to the wizard
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note If there were multiple instances of the same wizard type
+ * added to this object type without using \ref wizard_args, then
+ * only the first wizard matching wizard_type will be removed.
+ */
+#define ast_sorcery_object_type_remove_wizard(sorcery, object_type_name, \
+	wizard_type_name, wizard_args) \
+	__ast_sorcery_object_type_remove_wizard((sorcery), (object_type_name), \
+		AST_MODULE, (wizard_type_name), (wizard_args))
+
 /*!
  * \brief Remove an object wizard mapping
  *
diff --git a/main/sorcery.c b/main/sorcery.c
index 8e148819988481dbc6fdec0e5aee3c9437c4b809..d8378457111225c34258665e4eb73ac055df80fb 100644
--- a/main/sorcery.c
+++ b/main/sorcery.c
@@ -109,6 +109,15 @@ struct ast_sorcery_object_wizard {
 
 	/*! \brief Wizard is acting as an object cache */
 	unsigned int caching:1;
+
+	/*! \brief Wizard is read_only */
+	unsigned int read_only:1;
+
+	/*! \brief Wizard allows others of the same type */
+	unsigned int allow_duplicates:1;
+
+	/*! \brief Wizard arguments */
+	char wizard_args[0];
 };
 
 /*! \brief Interface for a sorcery object type wizards */
@@ -802,6 +811,35 @@ int ast_sorcery_get_wizard_mapping(struct ast_sorcery *sorcery,
 	return 0;
 }
 
+int __ast_sorcery_object_type_remove_wizard(struct ast_sorcery *sorcery,
+	const char *object_type_name, const char *module, const char *wizard_type_name,
+	const char *wizard_args)
+{
+	RAII_VAR(struct ast_sorcery_object_type *, object_type,
+		ao2_find(sorcery->types, object_type_name, OBJ_SEARCH_KEY), ao2_cleanup);
+	int res = -1;
+	int i;
+
+	if (!object_type) {
+		return res;
+	}
+
+	AST_VECTOR_RW_WRLOCK(&object_type->wizards);
+	for (i = 0; i < AST_VECTOR_SIZE(&object_type->wizards); i++) {
+		struct ast_sorcery_object_wizard *wizard = AST_VECTOR_GET(&object_type->wizards, i);
+
+		if (strcmp(wizard->wizard->callbacks.name, wizard_type_name) == 0
+			&& strcmp(S_OR(wizard->wizard_args, ""), S_OR(wizard_args, "")) == 0) {
+			ao2_cleanup(AST_VECTOR_REMOVE_ORDERED(&object_type->wizards, i));
+			res = 0;
+			break;
+		}
+	}
+	AST_VECTOR_RW_UNLOCK(&object_type->wizards);
+
+	return res;
+}
+
 /*! \brief Internal function removes a wizard mapping */
 int __ast_sorcery_remove_wizard_mapping(struct ast_sorcery *sorcery,
 		const char *type, const char *module, const char *name)
@@ -822,29 +860,35 @@ int __ast_sorcery_remove_wizard_mapping(struct ast_sorcery *sorcery,
 	return res;
 }
 
-/*! \brief Internal function which creates an object type and inserts a wizard mapping */
-enum ast_sorcery_apply_result __ast_sorcery_insert_wizard_mapping(struct ast_sorcery *sorcery,
-		const char *type, const char *module, const char *name, const char *data,
-		unsigned int caching, int position)
+enum ast_sorcery_apply_result __ast_sorcery_object_type_insert_wizard(struct ast_sorcery *sorcery,
+	const char *object_type_name, const char *module, const char *wizard_type_name,
+	const char *wizard_args, enum ast_sorcery_wizard_apply_flags flags, int position,
+	struct ast_sorcery_wizard **wizard, void **wizard_data)
 {
-	RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
-	RAII_VAR(struct ast_sorcery_internal_wizard *, wizard, ao2_find(wizards, name, OBJ_KEY), ao2_cleanup);
-	RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, ao2_alloc(sizeof(*object_wizard), sorcery_object_wizard_destructor), ao2_cleanup);
+	RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, object_type_name, OBJ_KEY), ao2_cleanup);
+	RAII_VAR(struct ast_sorcery_internal_wizard *, internal_wizard, ao2_find(wizards, wizard_type_name, OBJ_KEY), ao2_cleanup);
+	RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup);
 	int created = 0;
 
+	object_wizard = ao2_alloc(sizeof(*object_wizard)
+		+ (ast_strlen_zero(wizard_args) ? 0 : strlen(wizard_args) + 1),
+		sorcery_object_wizard_destructor);
+
 	if (!object_wizard) {
 		return AST_SORCERY_APPLY_FAIL;
 	}
 
-	if (!wizard || wizard->callbacks.module != ast_module_running_ref(wizard->callbacks.module)) {
-		ast_log(LOG_ERROR, "Wizard '%s' could not be applied to object type '%s' as it was not found\n",
-			name, type);
+	if (!internal_wizard
+		|| internal_wizard->callbacks.module != ast_module_running_ref(internal_wizard->callbacks.module)) {
+		ast_log(LOG_ERROR,
+			"Wizard '%s' could not be applied to object type '%s' as it was not found\n",
+			wizard_type_name, object_type_name);
 		return AST_SORCERY_APPLY_FAIL;
 	}
 
 	if (!object_type) {
-		if (!(object_type = sorcery_object_type_alloc(type, module))) {
-			ast_module_unref(wizard->callbacks.module);
+		if (!(object_type = sorcery_object_type_alloc(object_type_name, module))) {
+			ast_module_unref(internal_wizard->callbacks.module);
 			return AST_SORCERY_APPLY_FAIL;
 		}
 		created = 1;
@@ -855,29 +899,34 @@ enum ast_sorcery_apply_result __ast_sorcery_insert_wizard_mapping(struct ast_sor
 		struct ast_sorcery_object_wizard *found;
 
 #define WIZARD_COMPARE(a, b) ((a)->wizard == (b))
-		found = AST_VECTOR_GET_CMP(&object_type->wizards, wizard, WIZARD_COMPARE);
+		found = AST_VECTOR_GET_CMP(&object_type->wizards, internal_wizard, WIZARD_COMPARE);
 #undef WIZARD_COMPARE
-		if (found) {
+		if (found
+			&& !((flags & AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE) || found->allow_duplicates)) {
 			ast_debug(1, "Wizard %s already applied to object type %s\n",
-					wizard->callbacks.name, object_type->name);
+				internal_wizard->callbacks.name, object_type->name);
 			AST_VECTOR_RW_UNLOCK(&object_type->wizards);
-			ast_module_unref(wizard->callbacks.module);
+			ast_module_unref(internal_wizard->callbacks.module);
 			return AST_SORCERY_APPLY_DUPLICATE;
 		}
 	}
 
 	ast_debug(5, "Calling wizard %s open callback on object type %s\n",
-		name, object_type->name);
-	if (wizard->callbacks.open && !(object_wizard->data = wizard->callbacks.open(data))) {
+		wizard_type_name, object_type->name);
+	if (internal_wizard->callbacks.open && !(object_wizard->data = internal_wizard->callbacks.open(wizard_args))) {
 		ast_log(LOG_WARNING, "Wizard '%s' failed to open mapping for object type '%s' with data: %s\n",
-			name, object_type->name, S_OR(data, ""));
+			wizard_type_name, object_type->name, S_OR(wizard_args, ""));
 		AST_VECTOR_RW_UNLOCK(&object_type->wizards);
-		ast_module_unref(wizard->callbacks.module);
+		ast_module_unref(internal_wizard->callbacks.module);
 		return AST_SORCERY_APPLY_FAIL;
 	}
 
-	object_wizard->wizard = ao2_bump(wizard);
-	object_wizard->caching = caching;
+	object_wizard->wizard = ao2_bump(internal_wizard);
+	object_wizard->caching = !!(flags & AST_SORCERY_WIZARD_APPLY_CACHING);
+	object_wizard->read_only = !!(flags & AST_SORCERY_WIZARD_APPLY_READONLY);
+	if (wizard_args) {
+		strcpy(object_wizard->wizard_args, wizard_args); /* Safe */
+	}
 
 	if (position == AST_SORCERY_WIZARD_POSITION_LAST) {
 		position = AST_VECTOR_SIZE(&object_type->wizards);
@@ -895,17 +944,36 @@ enum ast_sorcery_apply_result __ast_sorcery_insert_wizard_mapping(struct ast_sor
 	}
 
 	NOTIFY_INSTANCE_OBSERVERS(sorcery->observers, wizard_mapped,
-		sorcery->module_name, sorcery, type, &wizard->callbacks, data, object_wizard->data);
+		sorcery->module_name, sorcery, object_type_name, &internal_wizard->callbacks, wizard_args,
+		object_wizard->data);
+
+	if (wizard) {
+		*wizard = &internal_wizard->callbacks;
+	}
+	if (wizard_data) {
+		*wizard_data = object_wizard->data;
+	}
 
 	return AST_SORCERY_APPLY_SUCCESS;
 }
 
+/*! \brief Internal function which creates an object type and inserts a wizard mapping */
+enum ast_sorcery_apply_result __ast_sorcery_insert_wizard_mapping(struct ast_sorcery *sorcery,
+		const char *object_type_name, const char *module, const char *wizard_type_name,
+		const char *wizard_args, unsigned int caching, int position)
+{
+	return __ast_sorcery_object_type_insert_wizard(sorcery, object_type_name, module, wizard_type_name,
+		wizard_args, caching ? AST_SORCERY_WIZARD_APPLY_CACHING : AST_SORCERY_WIZARD_APPLY_NONE,
+		position, NULL, NULL);
+}
+
 /*! \brief Internal function which creates an object type and adds a wizard mapping */
 enum ast_sorcery_apply_result __ast_sorcery_apply_wizard_mapping(struct ast_sorcery *sorcery,
-		const char *type, const char *module, const char *name, const char *data, unsigned int caching)
+		const char *object_type_name, const char *module, const char *wizard_type_name,
+		const char *wizard_args, unsigned int caching)
 {
-	return __ast_sorcery_insert_wizard_mapping(sorcery, type, module, name, data,
-		caching, AST_SORCERY_WIZARD_POSITION_LAST);
+	return __ast_sorcery_insert_wizard_mapping(sorcery, object_type_name, module, wizard_type_name,
+		wizard_args, caching, AST_SORCERY_WIZARD_POSITION_LAST);
 }
 
 enum ast_sorcery_apply_result  __ast_sorcery_apply_config(struct ast_sorcery *sorcery, const char *name, const char *module)
@@ -1904,7 +1972,7 @@ struct ao2_container *ast_sorcery_retrieve_by_prefix(const struct ast_sorcery *s
 /*! \brief Internal function which returns if the wizard has created the object */
 static int sorcery_wizard_create(const struct ast_sorcery_object_wizard *object_wizard, const struct sorcery_details *details)
 {
-	if (!object_wizard->wizard->callbacks.create) {
+	if (!object_wizard->wizard->callbacks.create || object_wizard->read_only) {
 		ast_debug(5, "Sorcery wizard '%s' does not support creation\n", object_wizard->wizard->callbacks.name);
 		return 0;
 	}
@@ -2015,7 +2083,7 @@ static int sorcery_observers_notify_update(void *data)
 /*! \brief Internal function which returns if a wizard has updated the object */
 static int sorcery_wizard_update(const struct ast_sorcery_object_wizard *object_wizard, const struct sorcery_details *details)
 {
-	if (!object_wizard->wizard->callbacks.update) {
+	if (!object_wizard->wizard->callbacks.update || object_wizard->read_only) {
 		ast_debug(5, "Sorcery wizard '%s' does not support updating\n", object_wizard->wizard->callbacks.name);
 		return 0;
 	}
@@ -2103,7 +2171,7 @@ static int sorcery_observers_notify_delete(void *data)
 /*! \brief Internal function which returns if a wizard has deleted the object */
 static int sorcery_wizard_delete(const struct ast_sorcery_object_wizard *object_wizard, const struct sorcery_details *details)
 {
-	if (!object_wizard->wizard->callbacks.delete) {
+	if (!object_wizard->wizard->callbacks.delete || object_wizard->read_only) {
 		ast_debug(5, "Sorcery wizard '%s' does not support deletion\n", object_wizard->wizard->callbacks.name);
 		return 0;
 	}
diff --git a/tests/test_sorcery.c b/tests/test_sorcery.c
index 06623369574a861e51c4bc67f4a4fdddac57dd8d..9f5cc7e8e86d0cf30e33c217b796cd5178ad8c79 100644
--- a/tests/test_sorcery.c
+++ b/tests/test_sorcery.c
@@ -3504,6 +3504,13 @@ AST_TEST_DEFINE(wizard_apply_and_insert)
 	ast_test_validate(test,
 		ast_sorcery_insert_wizard_mapping(sorcery, "test_object_type", "test2", "test2data", 0, 0) != 0);
 
+	ast_test_validate(test,
+		ast_sorcery_object_type_insert_wizard(sorcery, "test_object_type", "test2", "test2data2",
+			AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE, 0, NULL, NULL) == 0);
+
+	ast_test_validate(test,
+		ast_sorcery_object_type_remove_wizard(sorcery, "test_object_type", "test2", "test2data2") == 0);
+
 	ast_test_validate(test,
 		ast_sorcery_get_wizard_mapping(sorcery, "test_object_type", 0, &wizard, &data) == 0);
 	ast_test_validate(test, strcmp("test2", wizard->name) == 0);
@@ -3554,6 +3561,73 @@ AST_TEST_DEFINE(wizard_apply_and_insert)
 	return AST_TEST_PASS;
 }
 
+static struct ast_sorcery_wizard test_read_only_wizard = {
+	.name = "test-read-only",
+	.retrieve_id = sorcery_test_retrieve_id,
+};
+
+AST_TEST_DEFINE(wizard_read_only)
+{
+	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
+	RAII_VAR(struct ast_sorcery_wizard *, wizard_read_only, &test_read_only_wizard, ast_sorcery_wizard_unregister);
+	RAII_VAR(struct ast_sorcery_wizard *, wizard1, &test_wizard, ast_sorcery_wizard_unregister);
+	RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup);
+	struct ast_sorcery_wizard *wizard;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "wizard_read_only";
+		info->category = "/main/sorcery/";
+		info->summary = "sorcery wizard read-only test";
+		info->description =
+			"sorcery wizard read-only test";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	wizard1->load = sorcery_test_load;
+	wizard1->reload = sorcery_test_load;
+
+	if (!(sorcery = ast_sorcery_open())) {
+		ast_test_status_update(test, "Failed to open a sorcery instance\n");
+		return AST_TEST_FAIL;
+	}
+
+	ast_sorcery_wizard_register(wizard_read_only);
+	ast_sorcery_wizard_register(wizard1);
+
+	if ((ast_sorcery_apply_default(sorcery, "test_object_type", "test-read-only", NULL) != AST_SORCERY_APPLY_SUCCESS) ||
+		ast_sorcery_internal_object_register(sorcery, "test_object_type", test_sorcery_object_alloc, NULL, NULL)) {
+		ast_test_status_update(test, "Failed to apply object defaults\n");
+		return AST_TEST_FAIL;
+	}
+
+	ast_test_validate(test,
+		ast_sorcery_get_wizard_mapping_count(sorcery, "test_object_type") == 1);
+
+	ast_test_validate(test,
+		ast_sorcery_object_type_apply_wizard(sorcery, "test_object_type",
+			"test", "test2data", AST_SORCERY_WIZARD_APPLY_READONLY, &wizard, NULL) == 0);
+
+	ast_test_validate(test, strcmp(wizard->name, wizard1->name) == 0);
+
+	ast_test_validate(test,
+		ast_sorcery_get_wizard_mapping_count(sorcery, "test_object_type") == 2);
+
+	if (!(obj = ast_sorcery_alloc(sorcery, "test_object_type", "blah"))) {
+		ast_test_status_update(test, "Failed to allocate a known object type\n");
+		return AST_TEST_FAIL;
+	}
+
+	if (ast_sorcery_create(sorcery, obj) == 0) {
+		ast_test_status_update(test, "Should not have created object using read-only wizard\n");
+		return AST_TEST_FAIL;
+	}
+
+	return AST_TEST_PASS;
+}
+
 static int unload_module(void)
 {
 	AST_TEST_UNREGISTER(wizard_registration);
@@ -3606,6 +3680,7 @@ static int unload_module(void)
 	AST_TEST_UNREGISTER(instance_observation);
 	AST_TEST_UNREGISTER(wizard_observation);
 	AST_TEST_UNREGISTER(wizard_apply_and_insert);
+	AST_TEST_UNREGISTER(wizard_read_only);
 
 	return 0;
 }
@@ -3662,6 +3737,7 @@ static int load_module(void)
 	AST_TEST_REGISTER(global_observation);
 	AST_TEST_REGISTER(instance_observation);
 	AST_TEST_REGISTER(wizard_observation);
+	AST_TEST_REGISTER(wizard_read_only);
 
 	return AST_MODULE_LOAD_SUCCESS;
 }