diff --git a/src/addons/badges/pages/issued-badge/issued-badge.html b/src/addons/badges/pages/issued-badge/issued-badge.html
index 5f06dc1d6..9686be66d 100644
--- a/src/addons/badges/pages/issued-badge/issued-badge.html
+++ b/src/addons/badges/pages/issued-badge/issued-badge.html
@@ -49,7 +49,7 @@
                 <ion-item class="ion-text-wrap" *ngIf="badge.issuercontact">
                     <ion-label>
                         <p class="item-heading">{{ 'addon.badges.contact' | translate}}</p>
-                        <p><a href="mailto:{{badge.issuercontact}}" core-link auto-login="no" [showBrowserWarning]="false">
+                        <p><a href="mailto:{{badge.issuercontact}}" core-link [autoLogin]="false" [showBrowserWarning]="false">
                                 {{ badge.issuercontact }}
                             </a></p>
                     </ion-label>
@@ -95,7 +95,7 @@
                 <ion-item class="ion-text-wrap" *ngIf="badge.imageauthoremail">
                     <ion-label>
                         <p class="item-heading">{{ 'addon.badges.imageauthoremail' | translate}}</p>
-                        <p><a href="mailto:{{badge.imageauthoremail}}" core-link auto-login="no" [showBrowserWarning]="false">
+                        <p><a href="mailto:{{badge.imageauthoremail}}" core-link [autoLogin]="false" [showBrowserWarning]="false">
                                 {{ badge.imageauthoremail }}
                             </a></p>
                     </ion-label>
@@ -103,7 +103,7 @@
                 <ion-item class="ion-text-wrap" *ngIf="badge.imageauthorurl">
                     <ion-label>
                         <p class="item-heading">{{ 'addon.badges.imageauthorurl' | translate}}</p>
-                        <p><a [href]="badge.imageauthorurl" core-link auto-login="no"> {{ badge.imageauthorurl }} </a></p>
+                        <p><a [href]="badge.imageauthorurl" core-link [autoLogin]="false"> {{ badge.imageauthorurl }} </a></p>
                     </ion-label>
                 </ion-item>
                 <ion-item class="ion-text-wrap" *ngIf="badge.imagecaption">
@@ -166,7 +166,7 @@
                     <ion-label>
                         <p class="item-heading">{{ 'addon.badges.issueremail' | translate}}</p>
                         <p>
-                            <a href="mailto:{{badge.endorsement.issueremail}}" core-link auto-login="no" [showBrowserWarning]="false">
+                            <a href="mailto:{{badge.endorsement.issueremail}}" core-link [autoLogin]="false" [showBrowserWarning]="false">
                                 {{ badge.endorsement.issueremail }}
                             </a>
                         </p>
@@ -175,7 +175,7 @@
                 <ion-item class="ion-text-wrap" *ngIf="badge.endorsement.issuerurl">
                     <ion-label>
                         <p class="item-heading">{{ 'addon.badges.issuerurl' | translate}}</p>
-                        <p><a [href]="badge.endorsement.issuerurl" core-link auto-login="no"> {{ badge.endorsement.issuerurl }} </a></p>
+                        <p><a [href]="badge.endorsement.issuerurl" core-link [autoLogin]="false"> {{ badge.endorsement.issuerurl }} </a></p>
                     </ion-label>
                 </ion-item>
                 <ion-item class="ion-text-wrap" *ngIf="badge.endorsement.dateissued">
@@ -187,7 +187,7 @@
                 <ion-item class="ion-text-wrap" *ngIf="badge.endorsement.claimid">
                     <ion-label>
                         <p class="item-heading">{{ 'addon.badges.claimid' | translate}}</p>
-                        <p><a [href]="badge.endorsement.claimid" core-link auto-login="no"> {{ badge.endorsement.claimid }} </a></p>
+                        <p><a [href]="badge.endorsement.claimid" core-link [autoLogin]="false"> {{ badge.endorsement.claimid }} </a></p>
                     </ion-label>
                 </ion-item>
                 <ion-item class="ion-text-wrap" *ngIf="badge.endorsement.claimcomment">
@@ -225,7 +225,7 @@
                     </ion-label>
                 </ion-item-divider>
                 <ion-item class="ion-text-wrap" *ngFor="let alignment of badge.alignment" [href]="alignment.targeturl" core-link
-                    auto-login="no">
+                    [autoLogin]="false">
                     <ion-label>
                         <p class="item-heading">{{ alignment.targetname }}</p>
                     </ion-label>
diff --git a/src/addons/block/timeline/components/events/events.ts b/src/addons/block/timeline/components/events/events.ts
index 041c22755..bb69312bc 100644
--- a/src/addons/block/timeline/components/events/events.ts
+++ b/src/addons/block/timeline/components/events/events.ts
@@ -19,6 +19,7 @@ import { CoreTextUtils } from '@services/utils/text';
 import { CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper';
 import { AddonBlockTimelineDayEvents } from '@addons/block/timeline/classes/section';
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Directive to render a list of events in course overview.
@@ -36,9 +37,9 @@ export class AddonBlockTimelineEventsComponent implements OnInit {
 
     @Input() events: AddonBlockTimelineDayEvents[] = []; // The events to render.
     @Input() course?: CoreEnrolledCourseDataWithOptions; // Whether to show the course name.
-    @Input() showInlineCourse = true; // Whether to show the course name within event items.
-    @Input() canLoadMore = false; // Whether more events can be loaded.
-    @Input() loadingMore = false; // Whether loading is ongoing.
+    @Input({ transform: toBoolean }) showInlineCourse = true; // Whether to show the course name within event items.
+    @Input({ transform: toBoolean }) canLoadMore = false; // Whether more events can be loaded.
+    @Input({ transform: toBoolean }) loadingMore = false; // Whether loading is ongoing.
     @Output() loadMore = new EventEmitter(); // Notify that more events should be loaded.
 
     colorizeIcons = false;
diff --git a/src/addons/calendar/components/calendar/calendar.ts b/src/addons/calendar/components/calendar/calendar.ts
index e2df77771..2920b3851 100644
--- a/src/addons/calendar/components/calendar/calendar.ts
+++ b/src/addons/calendar/components/calendar/calendar.ts
@@ -53,6 +53,7 @@ import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
 import { CoreUrl } from '@singletons/url';
 import { CoreTime } from '@singletons/time';
 import { Translate } from '@singletons';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays a calendar.
@@ -69,9 +70,9 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
     @Input() initialYear?: number; // Initial year to load.
     @Input() initialMonth?: number; // Initial month to load.
     @Input() filter?: AddonCalendarFilter; // Filter to apply.
-    @Input() hidden?: boolean; // Whether the component is hidden.
-    @Input() canNavigate?: string | boolean; // Whether to include arrows to change the month. Defaults to true.
-    @Input() displayNavButtons?: string | boolean; // Whether to display nav buttons created by this component. Defaults to true.
+    @Input({ transform: toBoolean }) hidden = false; // Whether the component is hidden.
+    @Input({ transform: toBoolean }) canNavigate = true; // Whether to include arrows to change the month
+    @Input({ transform: toBoolean }) displayNavButtons = true; // Whether to display nav buttons created by this component.
     @Output() onEventClicked = new EventEmitter<number>();
     @Output() onDayClicked = new EventEmitter<{day: number; month: number; year: number}>();
 
@@ -145,10 +146,6 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
      * @inheritdoc
      */
     ngOnInit(): void {
-        this.canNavigate = typeof this.canNavigate == 'undefined' ? true : CoreUtils.isTrueOrOne(this.canNavigate);
-        this.displayNavButtons = typeof this.displayNavButtons == 'undefined' ? true :
-            CoreUtils.isTrueOrOne(this.displayNavButtons);
-
         const source = new AddonCalendarMonthSlidesItemsManagerSource(this, moment({
             year: this.initialYear,
             month: this.initialMonth ? this.initialMonth - 1 : undefined,
diff --git a/src/addons/calendar/pages/event/event.html b/src/addons/calendar/pages/event/event.html
index 6f21e79ef..b7f1ff8e4 100644
--- a/src/addons/calendar/pages/event/event.html
+++ b/src/addons/calendar/pages/event/event.html
@@ -104,7 +104,7 @@
                 <ion-label>
                     <p class="item-heading">{{ 'core.location' | translate}}</p>
                     <p>
-                        <a [href]="event.encodedLocation" core-link auto-login="no">
+                        <a [href]="event.encodedLocation" core-link [autoLogin]="false">
                             <core-format-text [text]="event.location" [contextLevel]="event.contextLevel"
                                 [contextInstanceId]="event.contextInstanceId" />
                         </a>
diff --git a/src/addons/mod/assign/classes/base-feedback-plugin-component.ts b/src/addons/mod/assign/classes/base-feedback-plugin-component.ts
index 31713b758..fd740ca95 100644
--- a/src/addons/mod/assign/classes/base-feedback-plugin-component.ts
+++ b/src/addons/mod/assign/classes/base-feedback-plugin-component.ts
@@ -18,6 +18,7 @@ import { CoreError } from '@classes/errors/error';
 import { CoreModals } from '@services/modals';
 import { AddonModAssignFeedbackCommentsTextData } from '../feedback/comments/services/handler';
 import { AddonModAssignAssign, AddonModAssignPlugin, AddonModAssignSubmission } from '../services/assign';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Base class for component to render a feedback plugin.
@@ -27,13 +28,13 @@ import { AddonModAssignAssign, AddonModAssignPlugin, AddonModAssignSubmission }
 })
 export class AddonModAssignFeedbackPluginBaseComponent implements IAddonModAssignFeedbackPluginComponent {
 
-    @Input() assign!: AddonModAssignAssign; // The assignment.
-    @Input() submission!: AddonModAssignSubmission; // The submission.
-    @Input() plugin!: AddonModAssignPlugin; // The plugin object.
-    @Input() userId!: number; // The user ID of the submission.
+    @Input({ required: true }) assign!: AddonModAssignAssign; // The assignment.
+    @Input({ required: true }) submission!: AddonModAssignSubmission; // The submission.
+    @Input({ required: true }) plugin!: AddonModAssignPlugin; // The plugin object.
+    @Input({ required: true }) userId!: number; // The user ID of the submission.
     @Input() configs?: Record<string,string>; // The configs for the plugin.
-    @Input() canEdit = false; // Whether the user can edit.
-    @Input() edit = false; // Whether the user is editing.
+    @Input({ transform: toBoolean }) canEdit = false; // Whether the user can edit.
+    @Input({ transform: toBoolean }) edit = false; // Whether the user is editing.
 
     /**
      * Open a modal to edit the feedback plugin.
diff --git a/src/addons/mod/assign/classes/base-submission-plugin-component.ts b/src/addons/mod/assign/classes/base-submission-plugin-component.ts
index 56f938d0b..f245c0a98 100644
--- a/src/addons/mod/assign/classes/base-submission-plugin-component.ts
+++ b/src/addons/mod/assign/classes/base-submission-plugin-component.ts
@@ -14,6 +14,7 @@
 
 import { Component, Input } from '@angular/core';
 import { AddonModAssignAssign, AddonModAssignPlugin, AddonModAssignSubmission } from '../services/assign';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Base class for component to render a submission plugin.
@@ -23,12 +24,12 @@ import { AddonModAssignAssign, AddonModAssignPlugin, AddonModAssignSubmission }
 })
 export class AddonModAssignSubmissionPluginBaseComponent {
 
-    @Input() assign!: AddonModAssignAssign; // The assignment.
-    @Input() submission!: AddonModAssignSubmission; // The submission.
-    @Input() plugin!: AddonModAssignPlugin; // The plugin object.
+    @Input({ required: true }) assign!: AddonModAssignAssign; // The assignment.
+    @Input({ required: true }) submission!: AddonModAssignSubmission; // The submission.
+    @Input({ required: true }) plugin!: AddonModAssignPlugin; // The plugin object.
     @Input() configs?: Record<string, string>; // The configs for the plugin.
-    @Input() edit = false; // Whether the user is editing.
-    @Input() allowOffline = false; // Whether to allow offline.
+    @Input({ transform: toBoolean }) edit = false; // Whether the user is editing.
+    @Input({ transform: toBoolean }) allowOffline = false; // Whether to allow offline.
 
     /**
      * Invalidate the data.
diff --git a/src/addons/mod/assign/components/edit-feedback-modal/edit-feedback-modal.ts b/src/addons/mod/assign/components/edit-feedback-modal/edit-feedback-modal.ts
index 35682d76f..aafd35365 100644
--- a/src/addons/mod/assign/components/edit-feedback-modal/edit-feedback-modal.ts
+++ b/src/addons/mod/assign/components/edit-feedback-modal/edit-feedback-modal.ts
@@ -37,10 +37,10 @@ import { AddonModAssignComponentsModule } from '../components.module';
 })
 export class AddonModAssignEditFeedbackModalComponent {
 
-    @Input() assign!: AddonModAssignAssign; // The assignment.
-    @Input() submission!: AddonModAssignSubmission; // The submission.
-    @Input() plugin!: AddonModAssignPlugin; // The plugin object.
-    @Input() userId!: number; // The user ID of the submission.
+    @Input({ required: true }) assign!: AddonModAssignAssign; // The assignment.
+    @Input({ required: true }) submission!: AddonModAssignSubmission; // The submission.
+    @Input({ required: true }) plugin!: AddonModAssignPlugin; // The plugin object.
+    @Input({ required: true }) userId!: number; // The user ID of the submission.
 
     @ViewChild('editFeedbackForm') formElement?: ElementRef;
 
diff --git a/src/addons/mod/assign/components/feedback-plugin/feedback-plugin.ts b/src/addons/mod/assign/components/feedback-plugin/feedback-plugin.ts
index 136e991b5..0d3fd81a5 100644
--- a/src/addons/mod/assign/components/feedback-plugin/feedback-plugin.ts
+++ b/src/addons/mod/assign/components/feedback-plugin/feedback-plugin.ts
@@ -25,6 +25,7 @@ import {
 import { AddonModAssignHelper, AddonModAssignPluginConfig } from '../../services/assign-helper';
 import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate';
 import { ADDON_MOD_ASSIGN_COMPONENT } from '../../constants';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays an assignment feedback plugin.
@@ -37,12 +38,12 @@ export class AddonModAssignFeedbackPluginComponent implements OnInit {
 
     @ViewChild(CoreDynamicComponent) dynamicComponent!: CoreDynamicComponent<IAddonModAssignFeedbackPluginComponent>;
 
-    @Input() assign!: AddonModAssignAssign; // The assignment.
-    @Input() submission!: AddonModAssignSubmission; // The submission.
-    @Input() plugin!: AddonModAssignPlugin; // The plugin object.
-    @Input() userId!: number; // The user ID of the submission.
-    @Input() canEdit = false; // Whether the user can edit.
-    @Input() edit = false; // Whether the user is editing.
+    @Input({ required: true }) assign!: AddonModAssignAssign; // The assignment.
+    @Input({ required: true }) submission!: AddonModAssignSubmission; // The submission.
+    @Input({ required: true }) plugin!: AddonModAssignPlugin; // The plugin object.
+    @Input({ required: true }) userId!: number; // The user ID of the submission.
+    @Input({ transform: toBoolean }) canEdit = false; // Whether the user can edit.
+    @Input({ transform: toBoolean }) edit = false; // Whether the user is editing.
 
     pluginComponent?: Type<IAddonModAssignFeedbackPluginComponent>; // Component to render the plugin.
     data?: AddonModAssignFeedbackPluginData; // Data to pass to the component.
diff --git a/src/addons/mod/assign/components/submission-plugin/submission-plugin.ts b/src/addons/mod/assign/components/submission-plugin/submission-plugin.ts
index 93210b120..4010a9193 100644
--- a/src/addons/mod/assign/components/submission-plugin/submission-plugin.ts
+++ b/src/addons/mod/assign/components/submission-plugin/submission-plugin.ts
@@ -25,6 +25,7 @@ import { AddonModAssignSubmissionDelegate } from '../../services/submission-dele
 import { CoreFileEntry } from '@services/file-helper';
 import type { AddonModAssignSubmissionPluginBaseComponent } from '@addons/mod/assign/classes/base-submission-plugin-component';
 import { ADDON_MOD_ASSIGN_COMPONENT } from '../../constants';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays an assignment submission plugin.
@@ -37,11 +38,11 @@ export class AddonModAssignSubmissionPluginComponent implements OnInit {
 
     @ViewChild(CoreDynamicComponent) dynamicComponent!: CoreDynamicComponent<AddonModAssignSubmissionPluginBaseComponent>;
 
-    @Input() assign!: AddonModAssignAssign; // The assignment.
-    @Input() submission!: AddonModAssignSubmission; // The submission.
-    @Input() plugin!: AddonModAssignPlugin; // The plugin object.
-    @Input() edit = false; // Whether the user is editing.
-    @Input() allowOffline = false; // Whether to allow offline.
+    @Input({ required: true }) assign!: AddonModAssignAssign; // The assignment.
+    @Input({ required: true }) submission!: AddonModAssignSubmission; // The submission.
+    @Input({ required: true }) plugin!: AddonModAssignPlugin; // The plugin object.
+    @Input({ transform: toBoolean }) edit = false; // Whether the user is editing.
+    @Input({ transform: toBoolean }) allowOffline = false; // Whether to allow offline.
 
     pluginComponent?: Type<AddonModAssignSubmissionPluginBaseComponent>; // Component to render the plugin.
     data?: AddonModAssignSubmissionPluginData; // Data to pass to the component.
diff --git a/src/addons/mod/assign/components/submission/submission.ts b/src/addons/mod/assign/components/submission/submission.ts
index 420b69701..5d4148030 100644
--- a/src/addons/mod/assign/components/submission/submission.ts
+++ b/src/addons/mod/assign/components/submission/submission.ts
@@ -82,9 +82,9 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
     @ViewChildren(AddonModAssignSubmissionPluginComponent) submissionComponents!:
         QueryList<AddonModAssignSubmissionPluginComponent>;
 
-    @Input() courseId!: number; // Course ID the submission belongs to.
-    @Input() moduleId!: number; // Module ID the submission belongs to.
-    @Input() submitId!: number; // User that did the submission.
+    @Input({ required: true }) courseId!: number; // Course ID the submission belongs to.
+    @Input({ required: true }) moduleId!: number; // Module ID the submission belongs to.
+    @Input() submitId!: number; // User that did the submission. Defaults to current user
     @Input() blindId?: number; // Blinded user ID (if it's blinded).
 
     loaded = false; // Whether data has been loaded.
diff --git a/src/addons/mod/chat/components/users-modal/users-modal.ts b/src/addons/mod/chat/components/users-modal/users-modal.ts
index 1ebd510e7..6f40a45fd 100644
--- a/src/addons/mod/chat/components/users-modal/users-modal.ts
+++ b/src/addons/mod/chat/components/users-modal/users-modal.ts
@@ -33,8 +33,8 @@ import { CoreSharedModule } from '@/core/shared.module';
 })
 export class AddonModChatUsersModalComponent implements OnInit, OnDestroy {
 
-    @Input() sessionId!: string;
-    @Input() cmId!: number;
+    @Input({ required: true }) sessionId!: string;
+    @Input({ required: true }) cmId!: number;
 
     users: AddonModChatUser[] = [];
     usersLoaded = false;
diff --git a/src/addons/mod/data/classes/base-field-plugin-component.ts b/src/addons/mod/data/classes/base-field-plugin-component.ts
index 93483a028..72a64b03c 100644
--- a/src/addons/mod/data/classes/base-field-plugin-component.ts
+++ b/src/addons/mod/data/classes/base-field-plugin-component.ts
@@ -26,8 +26,8 @@ import { AddonModDataTemplateMode } from '../constants';
 })
 export abstract class AddonModDataFieldPluginBaseComponent implements OnInit, OnChanges {
 
-    @Input() mode!: AddonModDataTemplateMode; // The render mode.
-    @Input() field!: AddonModDataField; // The field to render.
+    @Input({ required: true }) mode!: AddonModDataTemplateMode; // The render mode.
+    @Input({ required: true }) field!: AddonModDataField; // The field to render.
     @Input() value?: Partial<AddonModDataEntryField>; // The value of the field.
     @Input() database?: AddonModDataData; // Database object.
     @Input() error?: string; // Error when editing.
diff --git a/src/addons/mod/data/components/action/action.ts b/src/addons/mod/data/components/action/action.ts
index 10022f9f8..57a9da66d 100644
--- a/src/addons/mod/data/components/action/action.ts
+++ b/src/addons/mod/data/components/action/action.ts
@@ -45,10 +45,10 @@ import {
 export class AddonModDataActionComponent implements OnInit {
 
     @Input() access?: AddonModDataGetDataAccessInformationWSResponse; // Access info.
-    @Input() mode!: AddonModDataTemplateMode; // The render mode.
-    @Input() action!: AddonModDataAction; // The field to render.
-    @Input() entry!: AddonModDataEntry; // The value of the field.
-    @Input() database!: AddonModDataData; // Database object.
+    @Input({ required: true }) mode!: AddonModDataTemplateMode; // The render mode.
+    @Input({ required: true }) action!: AddonModDataAction; // The field to render.
+    @Input({ required: true }) entry!: AddonModDataEntry; // The value of the field.
+    @Input({ required: true }) database!: AddonModDataData; // Database object.
     @Input() title = ''; // Name of the module.
     @Input() group = 0; // Module group.
     @Input() offset?: number; // Offset of the entry.
diff --git a/src/addons/mod/data/components/field-plugin/field-plugin.ts b/src/addons/mod/data/components/field-plugin/field-plugin.ts
index 3527ccee3..f364c1a67 100644
--- a/src/addons/mod/data/components/field-plugin/field-plugin.ts
+++ b/src/addons/mod/data/components/field-plugin/field-plugin.ts
@@ -32,8 +32,8 @@ export class AddonModDataFieldPluginComponent implements OnInit, OnChanges {
 
     @ViewChild(CoreDynamicComponent) dynamicComponent?: CoreDynamicComponent<AddonModDataFieldPluginBaseComponent>;
 
-    @Input() mode!: AddonModDataTemplateMode; // The render mode.
-    @Input() field!: AddonModDataField; // The field to render.
+    @Input({ required: true }) mode!: AddonModDataTemplateMode; // The render mode.
+    @Input({ required: true }) field!: AddonModDataField; // The field to render.
     @Input() value?: unknown; // The value of the field.
     @Input() database?: AddonModDataData; // Database object.
     @Input() error?: string; // Error when editing.
diff --git a/src/addons/mod/data/components/search-modal/search-modal.ts b/src/addons/mod/data/components/search-modal/search-modal.ts
index 2867c9fea..9f7cf1086 100644
--- a/src/addons/mod/data/components/search-modal/search-modal.ts
+++ b/src/addons/mod/data/components/search-modal/search-modal.ts
@@ -50,9 +50,9 @@ export class AddonModDataSearchModalComponent implements OnInit {
 
     @ViewChild('searchFormEl') formElement!: ElementRef;
 
-    @Input() search!: AddonModDataSearchDataParams;
-    @Input() fields!: Record<number, AddonModDataField>;
-    @Input() database!: AddonModDataData;
+    @Input({ required: true }) search!: AddonModDataSearchDataParams;
+    @Input({ required: true }) fields!: Record<number, AddonModDataField>;
+    @Input({ required: true }) database!: AddonModDataData;
 
     advancedSearch = '';
     advancedIndexed: CoreFormFields = {};
diff --git a/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts b/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts
index 0eb646d2d..91e3041fa 100644
--- a/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts
+++ b/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts
@@ -30,9 +30,9 @@ import { CoreToasts } from '@services/toasts';
 })
 export class AddonModForumDiscussionOptionsMenuComponent implements OnInit {
 
-    @Input() discussion!: AddonModForumDiscussion; // The discussion.
-    @Input() forumId!: number; // The forum Id.
-    @Input() cmId!: number; // The component module Id.
+    @Input({ required: true }) discussion!: AddonModForumDiscussion; // The discussion.
+    @Input({ required: true }) forumId!: number; // The forum Id.
+    @Input({ required: true }) cmId!: number; // The component module Id.
 
     canPin = false;
 
diff --git a/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts b/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts
index 8dccdcb02..e7f44e28b 100644
--- a/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts
+++ b/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts
@@ -30,9 +30,9 @@ import { CoreNetworkError } from '@classes/errors/network-error';
 })
 export class AddonModForumPostOptionsMenuComponent implements OnInit {
 
-    @Input() post!: AddonModForumPost; // The post.
-    @Input() cmId!: number;
-    @Input() forumId!: number; // The forum Id.
+    @Input({ required: true }) post!: AddonModForumPost; // The post.
+    @Input({ required: true }) cmId!: number;
+    @Input({ required: true }) forumId!: number; // The forum Id.
 
     canEdit = false;
     canDelete = false;
diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts
index 759782675..0662f5a32 100644
--- a/src/addons/mod/forum/components/post/post.ts
+++ b/src/addons/mod/forum/components/post/post.ts
@@ -54,6 +54,7 @@ import { CoreDom } from '@singletons/dom';
 import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
 import { ADDON_MOD_FORUM_CHANGE_DISCUSSION_EVENT, ADDON_MOD_FORUM_COMPONENT } from '../../constants';
 import { CoreToasts } from '@services/toasts';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
@@ -65,21 +66,21 @@ import { CoreToasts } from '@services/toasts';
 })
 export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges {
 
-    @Input() post!: AddonModForumPost; // Post.
-    @Input() courseId!: number; // Post's course ID.
-    @Input() discussionId!: number; // Post's' discussion ID.
+    @Input({ required: true }) post!: AddonModForumPost; // Post.
+    @Input({ required: true }) courseId!: number; // Post's course ID.
+    @Input({ required: true }) discussionId!: number; // Post's' discussion ID.
     @Input() discussion?: AddonModForumDiscussion; // Post's' discussion, only for starting posts.
-    @Input() component!: string; // Component this post belong to.
-    @Input() componentId!: number; // Component ID.
-    @Input() formData!: AddonModForumSharedPostFormData; // Object with the new post data. Usually shared between posts.
-    @Input() originalData!: Omit<AddonModForumPostFormData, 'id'>; // Original post data. Usually shared between posts.
-    @Input() trackPosts!: boolean; // True if post is being tracked.
-    @Input() forum!: AddonModForumData; // The forum the post belongs to. Required for attachments and offline posts.
-    @Input() accessInfo!: AddonModForumAccessInformation; // Forum access information.
+    @Input({ required: true }) component!: string; // Component this post belong to.
+    @Input({ required: true }) componentId!: number; // Component ID.
+    @Input({ required: true }) formData!: AddonModForumSharedPostFormData; // New post data. Usually shared between posts.
+    @Input({ required: true }) originalData!: Omit<AddonModForumPostFormData, 'id'>; // Original data. Usually shared between posts.
+    @Input({ required: true, transform: toBoolean }) trackPosts = false; // True if post is being tracked.
+    @Input({ required: true }) forum!: AddonModForumData; // The forum the post belongs to.
+    @Input({ required: true }) accessInfo!: AddonModForumAccessInformation; // Forum access information.
     @Input() parentSubject?: string; // Subject of parent post.
     @Input() ratingInfo?: CoreRatingInfo; // Rating info item.
-    @Input() leavingPage?: boolean; // Whether the page that contains this post is being left and will be destroyed.
-    @Input() highlight = false;
+    @Input({ transform: toBoolean }) leavingPage = false; // Whether the page that contains this post is being left.
+    @Input({ transform: toBoolean }) highlight = false;
     @Output() onPostChange: EventEmitter<void> = new EventEmitter<void>(); // Event emitted when a reply is posted or modified.
 
     @ViewChild('replyFormEl') formElement!: ElementRef;
diff --git a/src/addons/mod/quiz/accessrules/offlineattempts/component/offlineattempts.ts b/src/addons/mod/quiz/accessrules/offlineattempts/component/offlineattempts.ts
index ea9238e3e..bcf26e137 100644
--- a/src/addons/mod/quiz/accessrules/offlineattempts/component/offlineattempts.ts
+++ b/src/addons/mod/quiz/accessrules/offlineattempts/component/offlineattempts.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz';
 import { AddonModQuizSync } from '@addons/mod/quiz/services/quiz-sync';
 import { Component, OnInit, Input } from '@angular/core';
@@ -29,7 +30,7 @@ export class AddonModQuizAccessOfflineAttemptsComponent implements OnInit {
     @Input() rule?: string; // The name of the rule.
     @Input() quiz?: AddonModQuizQuizWSData; // The quiz the rule belongs to.
     @Input() attempt?: AddonModQuizAttemptWSData; // The attempt being started/continued.
-    @Input() prefetch?: boolean; // Whether the user is prefetching the quiz.
+    @Input({ transform: toBoolean }) prefetch = false; // Whether the user is prefetching the quiz.
     @Input() siteId?: string; // Site ID.
     @Input() form?: FormGroup; // Form where to add the form control.
 
diff --git a/src/addons/mod/quiz/accessrules/password/component/password.ts b/src/addons/mod/quiz/accessrules/password/component/password.ts
index 77ce32ac5..65e3215dc 100644
--- a/src/addons/mod/quiz/accessrules/password/component/password.ts
+++ b/src/addons/mod/quiz/accessrules/password/component/password.ts
@@ -16,6 +16,7 @@ import { Component, OnInit, Input } from '@angular/core';
 import { FormGroup, FormBuilder } from '@angular/forms';
 
 import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to render the preflight for password.
@@ -29,7 +30,7 @@ export class AddonModQuizAccessPasswordComponent implements OnInit {
     @Input() rule?: string; // The name of the rule.
     @Input() quiz?: AddonModQuizQuizWSData; // The quiz the rule belongs to.
     @Input() attempt?: AddonModQuizAttemptWSData; // The attempt being started/continued.
-    @Input() prefetch?: boolean; // Whether the user is prefetching the quiz.
+    @Input({ transform: toBoolean }) prefetch = false; // Whether the user is prefetching the quiz.
     @Input() siteId?: string; // Site ID.
     @Input() form?: FormGroup; // Form where to add the form control.
 
diff --git a/src/addons/mod/quiz/accessrules/timelimit/component/timelimit.ts b/src/addons/mod/quiz/accessrules/timelimit/component/timelimit.ts
index b66ae5b3c..b702e889a 100644
--- a/src/addons/mod/quiz/accessrules/timelimit/component/timelimit.ts
+++ b/src/addons/mod/quiz/accessrules/timelimit/component/timelimit.ts
@@ -17,6 +17,7 @@ import { FormGroup } from '@angular/forms';
 
 import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz';
 import { CoreTime } from '@singletons/time';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to render the preflight for time limit.
@@ -30,7 +31,7 @@ export class AddonModQuizAccessTimeLimitComponent implements OnInit {
     @Input() rule?: string; // The name of the rule.
     @Input() quiz?: AddonModQuizQuizWSData; // The quiz the rule belongs to.
     @Input() attempt?: AddonModQuizAttemptWSData; // The attempt being started/continued.
-    @Input() prefetch?: boolean; // Whether the user is prefetching the quiz.
+    @Input({ transform: toBoolean }) prefetch = false; // Whether the user is prefetching the quiz.
     @Input() siteId?: string; // Site ID.
     @Input() form?: FormGroup; // Form where to add the form control.
 
diff --git a/src/addons/mod/quiz/components/attempt-info/attempt-info.ts b/src/addons/mod/quiz/components/attempt-info/attempt-info.ts
index 25c0498ac..4e6ef30c6 100644
--- a/src/addons/mod/quiz/components/attempt-info/attempt-info.ts
+++ b/src/addons/mod/quiz/components/attempt-info/attempt-info.ts
@@ -30,8 +30,8 @@ import { isSafeNumber } from '@/core/utils/types';
 })
 export class AddonModQuizAttemptInfoComponent implements OnChanges {
 
-    @Input() quiz!: AddonModQuizQuizData;
-    @Input() attempt!: AddonModQuizAttempt;
+    @Input({ required: true }) quiz!: AddonModQuizQuizData;
+    @Input({ required: true }) attempt!: AddonModQuizAttempt;
     @Input() additionalData?: AddonModQuizWSAdditionalData[]; // Additional data to display for the attempt.
 
     isFinished = false;
diff --git a/src/addons/mod/quiz/components/attempt-state/attempt-state.ts b/src/addons/mod/quiz/components/attempt-state/attempt-state.ts
index dd165198a..11bae29df 100644
--- a/src/addons/mod/quiz/components/attempt-state/attempt-state.ts
+++ b/src/addons/mod/quiz/components/attempt-state/attempt-state.ts
@@ -14,6 +14,7 @@
 
 import { Component, Input, OnChanges } from '@angular/core';
 import { AddonModQuiz } from '../../services/quiz';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays an attempt state.
@@ -26,7 +27,7 @@ import { AddonModQuiz } from '../../services/quiz';
 export class AddonModQuizAttemptStateComponent implements OnChanges {
 
     @Input() state = '';
-    @Input() finishedOffline = false;
+    @Input({ transform: toBoolean }) finishedOffline = false;
 
     readableState = '';
     color = '';
diff --git a/src/addons/mod/quiz/components/navigation-modal/navigation-modal.ts b/src/addons/mod/quiz/components/navigation-modal/navigation-modal.ts
index 1b71ea075..5950c7b04 100644
--- a/src/addons/mod/quiz/components/navigation-modal/navigation-modal.ts
+++ b/src/addons/mod/quiz/components/navigation-modal/navigation-modal.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input } from '@angular/core';
 
 import { CoreQuestionQuestionParsed } from '@features/question/services/question';
@@ -32,11 +33,11 @@ import { ModalController } from '@singletons';
 export class AddonModQuizNavigationModalComponent {
 
     @Input() navigation?: AddonModQuizNavigationQuestion[]; // Whether the user is reviewing the attempt.
-    @Input() summaryShown?: boolean; // Whether summary is currently being shown.
+    @Input({ transform: toBoolean }) summaryShown = false; // Whether summary is currently being shown.
     @Input() nextPage?: number; // Next page.
     @Input() currentPage?: number; // Current page.
-    @Input() isReview?: boolean; // Whether the user is reviewing the attempt.
-    @Input() isSequential?: boolean; // Whether quiz navigation is sequential.
+    @Input({ transform: toBoolean }) isReview = false; // Whether the user is reviewing the attempt.
+    @Input({ transform: toBoolean }) isSequential = false; // Whether quiz navigation is sequential.
 
     /**
      * Close modal.
diff --git a/src/addons/mod/quiz/components/preflight-modal/preflight-modal.ts b/src/addons/mod/quiz/components/preflight-modal/preflight-modal.ts
index 4ec740b22..975f4ae42 100644
--- a/src/addons/mod/quiz/components/preflight-modal/preflight-modal.ts
+++ b/src/addons/mod/quiz/components/preflight-modal/preflight-modal.ts
@@ -23,6 +23,7 @@ import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-dele
 import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '../../services/quiz';
 import { CoreDom } from '@singletons/dom';
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Modal that renders the access rules for a quiz.
@@ -39,12 +40,12 @@ export class AddonModQuizPreflightModalComponent implements OnInit {
 
     @ViewChild('preflightFormEl') formElement?: ElementRef;
 
-    @Input() title!: string;
+    @Input({ required: true }) title!: string;
     @Input() quiz?: AddonModQuizQuizWSData;
     @Input() attempt?: AddonModQuizAttemptWSData;
-    @Input() prefetch?: boolean;
-    @Input() siteId!: string;
-    @Input() rules!: string[];
+    @Input({ transform: toBoolean }) prefetch = false;
+    @Input({ required: true }) siteId!: string;
+    @Input({ required: true }) rules!: string[];
 
     preflightForm: FormGroup;
     accessRulesData: { component: Type<unknown>; data: Record<string, unknown>}[] = []; // Component and data for each access rule.
diff --git a/src/addons/mod/quiz/components/question-card/question-card.ts b/src/addons/mod/quiz/components/question-card/question-card.ts
index 0f6c4c87a..b89c912cb 100644
--- a/src/addons/mod/quiz/components/question-card/question-card.ts
+++ b/src/addons/mod/quiz/components/question-card/question-card.ts
@@ -25,6 +25,6 @@ import { CoreQuestionQuestionForView } from '@features/question/services/questio
 })
 export class AddonModQuizQuestionCardComponent {
 
-    @Input() question!: CoreQuestionQuestionForView;
+    @Input({ required: true }) question!: CoreQuestionQuestionForView;
 
 }
diff --git a/src/addons/mod/scorm/components/toc/toc.ts b/src/addons/mod/scorm/components/toc/toc.ts
index 42199ba11..f91d0bbcf 100644
--- a/src/addons/mod/scorm/components/toc/toc.ts
+++ b/src/addons/mod/scorm/components/toc/toc.ts
@@ -35,9 +35,9 @@ export class AddonModScormTocComponent implements OnInit {
     @Input() toc: AddonModScormTOCScoWithIcon[] = [];
     @Input() attemptToContinue?: number;
     @Input() selected?: number;
-    @Input() moduleId!: number;
-    @Input() courseId!: number;
-    @Input() accessInfo!: AddonModScormGetScormAccessInformationWSResponse;
+    @Input({ required: true }) moduleId!: number;
+    @Input({ required: true }) courseId!: number;
+    @Input({ required: true }) accessInfo!: AddonModScormGetScormAccessInformationWSResponse;
     @Input() mode = '';
 
     isBrowse = false;
diff --git a/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts b/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts
index 878d918d3..adfb4821a 100644
--- a/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts
+++ b/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts
@@ -32,7 +32,7 @@ export class AddonModWikiSubwikiPickerComponent {
 
     @Input() courseId?: number;
     @Input() subwikis: AddonModWikiSubwikiListGrouping[] = [];
-    @Input() currentSubwiki!: AddonModWikiSubwiki;
+    @Input({ required: true }) currentSubwiki!: AddonModWikiSubwiki;
 
     /**
      * Checks if the given subwiki is the one currently selected.
diff --git a/src/addons/mod/workshop/classes/assessment-strategy-component.ts b/src/addons/mod/workshop/classes/assessment-strategy-component.ts
index 1e9c85548..d9ffa8626 100644
--- a/src/addons/mod/workshop/classes/assessment-strategy-component.ts
+++ b/src/addons/mod/workshop/classes/assessment-strategy-component.ts
@@ -15,6 +15,7 @@
 import { Component, Input } from '@angular/core';
 import { AddonModWorkshopGetAssessmentFormFieldsParsedData } from '../services/workshop';
 import { AddonModWorkshopSubmissionAssessmentWithFormData } from '../services/workshop-helper';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Base class for component to render an assessment strategy.
@@ -24,13 +25,13 @@ import { AddonModWorkshopSubmissionAssessmentWithFormData } from '../services/wo
 })
 export class AddonModWorkshopAssessmentStrategyBaseComponent {
 
-    @Input() workshopId!: number;
-    @Input() assessment!: AddonModWorkshopSubmissionAssessmentWithFormData;
-    @Input() edit!: boolean;
-    @Input() selectedValues!: AddonModWorkshopGetAssessmentFormFieldsParsedData[];
-    @Input() fieldErrors!: Record<string, string>;
-    @Input() strategy!: string;
-    @Input() moduleId!: number;
+    @Input({ required: true }) workshopId!: number;
+    @Input({ required: true }) assessment!: AddonModWorkshopSubmissionAssessmentWithFormData;
+    @Input({ required: true, transform: toBoolean }) edit = false;
+    @Input({ required: true }) selectedValues!: AddonModWorkshopGetAssessmentFormFieldsParsedData[];
+    @Input({ required: true }) fieldErrors!: Record<string, string>;
+    @Input({ required: true }) strategy!: string;
+    @Input({ required: true }) moduleId!: number;
     @Input() courseId?: number;
 
 }
diff --git a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts
index 503174cf5..1c1908047 100644
--- a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts
+++ b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts
@@ -42,6 +42,7 @@ import {
     ADDON_MOD_WORKSHOP_COMPONENT,
     AddonModWorkshopOverallFeedbackMode,
 } from '@addons/mod/workshop/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays workshop assessment strategy form.
@@ -52,12 +53,12 @@ import {
 })
 export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDestroy {
 
-    @Input() workshop!: AddonModWorkshopData;
-    @Input() access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
-    @Input() assessmentId!: number;
-    @Input() userId!: number;
-    @Input() strategy!: string;
-    @Input() edit = false;
+    @Input({ required: true }) workshop!: AddonModWorkshopData;
+    @Input({ required: true }) access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
+    @Input({ required: true }) assessmentId!: number;
+    @Input({ required: true }) userId!: number;
+    @Input({ required: true }) strategy!: string;
+    @Input({ transform: toBoolean }) edit = false;
 
     @ViewChild('assessmentForm') formElement!: ElementRef;
 
diff --git a/src/addons/mod/workshop/components/assessment/assessment.ts b/src/addons/mod/workshop/components/assessment/assessment.ts
index 1e6464bc0..6fac33bd2 100644
--- a/src/addons/mod/workshop/components/assessment/assessment.ts
+++ b/src/addons/mod/workshop/components/assessment/assessment.ts
@@ -36,12 +36,12 @@ import { AddonModWorkshopOffline } from '../../services/workshop-offline';
 })
 export class AddonModWorkshopAssessmentComponent implements OnInit {
 
-    @Input() assessment!: AddonModWorkshopSubmissionAssessmentWithFormData;
-    @Input() courseId!: number;
-    @Input() workshop!: AddonModWorkshopData;
-    @Input() access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
-    @Input() submission!: AddonModWorkshopSubmissionDataWithOfflineData;
-    @Input() module!: CoreCourseModuleData;
+    @Input({ required: true }) assessment!: AddonModWorkshopSubmissionAssessmentWithFormData;
+    @Input({ required: true }) courseId!: number;
+    @Input({ required: true }) workshop!: AddonModWorkshopData;
+    @Input({ required: true }) access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
+    @Input({ required: true }) submission!: AddonModWorkshopSubmissionDataWithOfflineData;
+    @Input({ required: true }) module!: CoreCourseModuleData;
 
     canViewAssessment = false;
     canSelfAssess = false;
diff --git a/src/addons/mod/workshop/components/phase-modal/phase-modal.ts b/src/addons/mod/workshop/components/phase-modal/phase-modal.ts
index 95bc52faa..a6a138526 100644
--- a/src/addons/mod/workshop/components/phase-modal/phase-modal.ts
+++ b/src/addons/mod/workshop/components/phase-modal/phase-modal.ts
@@ -18,6 +18,7 @@ import { ModalController } from '@singletons';
 import { AddonModWorkshopPhaseData, AddonModWorkshopPhaseTaskData } from '../../services/workshop';
 import { AddonModWorkshopPhase } from '../../constants';
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Page that displays the phase info modal.
@@ -31,10 +32,10 @@ import { CoreSharedModule } from '@/core/shared.module';
 })
 export class AddonModWorkshopPhaseInfoModalComponent implements OnInit {
 
-    @Input() phases!: AddonModWorkshopPhaseDataWithSwitch[];
-    @Input() workshopPhase!: AddonModWorkshopPhase;
-    @Input() showSubmit = false;
-    @Input() externalUrl!: string;
+    @Input({ required: true }) phases!: AddonModWorkshopPhaseDataWithSwitch[];
+    @Input({ required: true }) workshopPhase!: AddonModWorkshopPhase;
+    @Input({ transform: toBoolean }) showSubmit = false;
+    @Input({ required: true }) externalUrl!: string;
 
     ngOnInit(): void {
 
diff --git a/src/addons/mod/workshop/components/submission/submission.ts b/src/addons/mod/workshop/components/submission/submission.ts
index 53ae4a891..0b3481f09 100644
--- a/src/addons/mod/workshop/components/submission/submission.ts
+++ b/src/addons/mod/workshop/components/submission/submission.ts
@@ -30,6 +30,7 @@ import {
 } from '../../services/workshop-helper';
 import { AddonModWorkshopOffline } from '../../services/workshop-offline';
 import { ADDON_MOD_WORKSHOP_COMPONENT, ADDON_MOD_WORKSHOP_PAGE_NAME, AddonModWorkshopPhase } from '@addons/mod/workshop/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays workshop submission.
@@ -41,13 +42,13 @@ import { ADDON_MOD_WORKSHOP_COMPONENT, ADDON_MOD_WORKSHOP_PAGE_NAME, AddonModWor
 })
 export class AddonModWorkshopSubmissionComponent implements OnInit {
 
-    @Input() submission!: AddonModWorkshopSubmissionDataWithOfflineData;
-    @Input() module!: CoreCourseModuleData;
-    @Input() workshop!: AddonModWorkshopData;
-    @Input() access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
-    @Input() courseId!: number;
+    @Input({ required: true }) submission!: AddonModWorkshopSubmissionDataWithOfflineData;
+    @Input({ required: true }) module!: CoreCourseModuleData;
+    @Input({ required: true }) workshop!: AddonModWorkshopData;
+    @Input({ required: true }) access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
+    @Input({ required: true }) courseId!: number;
     @Input() assessment?: AddonModWorkshopSubmissionAssessmentWithFormData;
-    @Input() summary = false;
+    @Input({ transform: toBoolean }) summary = false;
 
     component = ADDON_MOD_WORKSHOP_COMPONENT;
     componentId?: number;
diff --git a/src/addons/mod/workshop/pages/submission/submission.html b/src/addons/mod/workshop/pages/submission/submission.html
index dde29a697..0a4ed5029 100644
--- a/src/addons/mod/workshop/pages/submission/submission.html
+++ b/src/addons/mod/workshop/pages/submission/submission.html
@@ -88,8 +88,8 @@
                     <h2>{{ 'addon.mod_workshop.givengrades' | translate }}</h2>
                 </ion-label>
             </ion-item-divider>
-            <addon-mod-workshop-assessment *ngFor="let reviewer of submissionInfo.reviewerof" [assessment]="reviewer" [courseId]="courseId"
-                [module]="module" [workshop]="workshop" [access]="access" />
+            <addon-mod-workshop-assessment *ngFor="let reviewer of submissionInfo.reviewerof" [submission]="submission"
+                [assessment]="reviewer" [courseId]="courseId" [module]="module" [workshop]="workshop" [access]="access" />
         </ion-list>
 
         <form [formGroup]="feedbackForm" *ngIf="canAddFeedback && submission" #feedbackFormEl>
diff --git a/src/addons/notes/components/add/add-modal.ts b/src/addons/notes/components/add/add-modal.ts
index 337b97dc6..c4c12b786 100644
--- a/src/addons/notes/components/add/add-modal.ts
+++ b/src/addons/notes/components/add/add-modal.ts
@@ -36,7 +36,7 @@ export class AddonNotesAddComponent {
 
     @ViewChild('itemEdit') formElement?: ElementRef;
 
-    @Input() courseId!: number;
+    @Input({ required: true }) courseId!: number;
     @Input() userId?: number;
     @Input() type: AddonNotesPublishState = 'personal';
     text = '';
diff --git a/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts b/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts
index ab931d06a..3214d4232 100644
--- a/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts
+++ b/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, Output, EventEmitter } from '@angular/core';
 
 import { CoreQuestionBehaviourButton, CoreQuestionQuestion } from '@features/question/services/question-helper';
@@ -30,11 +31,11 @@ export class AddonQbehaviourDeferredCBMComponent {
     @Input() component?: string; // The component the question belongs to.
     @Input() componentId?: number; // ID of the component the question belongs to.
     @Input() attemptId?: number; // Attempt ID.
-    @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
+    @Input({ transform: toBoolean }) offlineEnabled = false; // Whether the question can be answered in offline.
     @Input() contextLevel?: ContextLevel; // The context level.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters.
-    @Input() review?: boolean; // Whether the user is in review mode.
+    @Input({ transform: toBoolean }) review = false; // Whether the user is in review mode.
     @Input() preferredBehaviour?: string; // Preferred behaviour.
     @Output() buttonClicked = new EventEmitter<CoreQuestionBehaviourButton>(); // Will emit when a behaviour button is clicked.
     @Output() onAbort = new EventEmitter<void>(); // Should emit an event if the question should be aborted.
diff --git a/src/addons/qbehaviour/informationitem/component/informationitem.ts b/src/addons/qbehaviour/informationitem/component/informationitem.ts
index f3a6a64a2..1dd6717c3 100644
--- a/src/addons/qbehaviour/informationitem/component/informationitem.ts
+++ b/src/addons/qbehaviour/informationitem/component/informationitem.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, Output, EventEmitter } from '@angular/core';
 
 import { CoreQuestionBehaviourButton, CoreQuestionQuestion } from '@features/question/services/question-helper';
@@ -30,11 +31,11 @@ export class AddonQbehaviourInformationItemComponent {
     @Input() component?: string; // The component the question belongs to.
     @Input() componentId?: number; // ID of the component the question belongs to.
     @Input() attemptId?: number; // Attempt ID.
-    @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
+    @Input({ transform: toBoolean }) offlineEnabled = false; // Whether the question can be answered in offline.
     @Input() contextLevel?: ContextLevel; // The context level.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters.
-    @Input() review?: boolean; // Whether the user is in review mode.
+    @Input({ transform: toBoolean }) review = false; // Whether the user is in review mode.
     @Input() preferredBehaviour?: string; // Preferred behaviour.
     @Output() buttonClicked = new EventEmitter<CoreQuestionBehaviourButton>(); // Will emit when a behaviour button is clicked.
     @Output() onAbort = new EventEmitter<void>(); // Should emit an event if the question should be aborted.
diff --git a/src/core/classes/tabs.ts b/src/core/classes/tabs.ts
index 08d76b564..cf0b19614 100644
--- a/src/core/classes/tabs.ts
+++ b/src/core/classes/tabs.ts
@@ -39,6 +39,7 @@ import { CoreDirectivesRegistry } from '@singletons/directives-registry';
 import { Swiper } from 'swiper';
 import { SwiperOptions } from 'swiper/types';
 import { CoreSwiper } from '@singletons/swiper';
+import { toBoolean } from '../transforms/boolean';
 
 /**
  * Class to abstract some common code for tabs.
@@ -52,7 +53,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements AfterViewIn
     protected static readonly MIN_TAB_WIDTH = 107;
 
     @Input() selectedIndex = 0; // Index of the tab to select.
-    @Input() hideUntil = false; // Determine when should the contents be shown.
+    @Input({ transform: toBoolean }) hideUntil = false; // Determine when should the contents be shown.
     @Output() protected ionChange = new EventEmitter<T>(); // Emitted when the tab changes.
 
     protected swiper?: Swiper;
diff --git a/src/core/components/attachments/attachments.ts b/src/core/components/attachments/attachments.ts
index ca67ff38a..c844e8206 100644
--- a/src/core/components/attachments/attachments.ts
+++ b/src/core/components/attachments/attachments.ts
@@ -25,6 +25,7 @@ import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuplo
 import { CoreFileEntry } from '@services/file-helper';
 import { CoreCourses } from '@features/courses/services/courses';
 import { CoreUtils } from '@services/utils/utils';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to render attachments, allow adding more and delete the current ones.
@@ -51,9 +52,9 @@ export class CoreAttachmentsComponent implements OnInit {
     @Input() maxSubmissions?: number; // Max number of attachments. -1 means unlimited, not defined means unknown limit.
     @Input() component?: string; // Component the downloaded files will be linked to.
     @Input() componentId?: string | number; // Component ID.
-    @Input() allowOffline?: boolean | string; // Whether to allow selecting files in offline.
+    @Input({ transform: toBoolean }) allowOffline = false; // Whether to allow selecting files in offline.
     @Input() acceptedTypes?: string; // List of supported filetypes. If undefined, all types supported.
-    @Input() required?: boolean; // Whether to display the required mark.
+    @Input({ transform: toBoolean }) required = false; // Whether to display the required mark.
     @Input() courseId?: number; // Course ID.
     @Input() title = Translate.instant('core.fileuploader.attachedfiles'); // Title to display.
 
@@ -131,9 +132,7 @@ export class CoreAttachmentsComponent implements OnInit {
      * Add a new attachment.
      */
     async add(): Promise<void> {
-        const allowOffline = !!this.allowOffline && this.allowOffline !== 'false';
-
-        if (!allowOffline && !CoreNetwork.isOnline()) {
+        if (!this.allowOffline && !CoreNetwork.isOnline()) {
             CoreDomUtils.showErrorModal('core.fileuploader.errormustbeonlinetoupload', true);
 
             return;
@@ -142,7 +141,7 @@ export class CoreAttachmentsComponent implements OnInit {
         const mimetypes = this.fileTypes && this.fileTypes.mimetypes;
 
         try {
-            const result = await CoreFileUploaderHelper.selectFile(this.maxSize, allowOffline, undefined, mimetypes);
+            const result = await CoreFileUploaderHelper.selectFile(this.maxSize, this.allowOffline, undefined, mimetypes);
 
             this.files?.push(result);
         } catch (error) {
diff --git a/src/core/components/bs-tooltip/bs-tooltip.ts b/src/core/components/bs-tooltip/bs-tooltip.ts
index f3738b099..3e7825ddb 100644
--- a/src/core/components/bs-tooltip/bs-tooltip.ts
+++ b/src/core/components/bs-tooltip/bs-tooltip.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input } from '@angular/core';
 
 /**
@@ -24,6 +25,6 @@ import { Component, Input } from '@angular/core';
 export class CoreBSTooltipComponent {
 
     @Input() content = '';
-    @Input() html?: boolean;
+    @Input({ transform: toBoolean }) html = false;
 
 }
diff --git a/src/core/components/button-with-spinner/button-with-spinner.ts b/src/core/components/button-with-spinner/button-with-spinner.ts
index 76cfa0c5c..ab6b611e7 100644
--- a/src/core/components/button-with-spinner/button-with-spinner.ts
+++ b/src/core/components/button-with-spinner/button-with-spinner.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input } from '@angular/core';
 import { CoreAnimations } from '@components/animations';
 
@@ -31,7 +32,7 @@ import { CoreAnimations } from '@components/animations';
 })
 export class CoreButtonWithSpinnerComponent {
 
-    @Input() loading = true;
+    @Input({ transform: toBoolean }) loading = true;
     @Input() loadingLabel = 'core.loading';
 
 }
diff --git a/src/core/components/chart/chart.ts b/src/core/components/chart/chart.ts
index dd566186d..5f5affbd3 100644
--- a/src/core/components/chart/chart.ts
+++ b/src/core/components/chart/chart.ts
@@ -13,10 +13,10 @@
 // limitations under the License.
 
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, OnDestroy, OnInit, ElementRef, OnChanges, ViewChild, SimpleChange } from '@angular/core';
 import { CoreFilter } from '@features/filter/services/filter';
 import { CoreFilterHelper } from '@features/filter/services/filter-helper';
-import { CoreUtils } from '@services/utils/utils';
 import { ChartLegendLabelItem, ChartLegendOptions } from 'chart.js';
 
 /**
@@ -50,11 +50,12 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges {
     @Input() type?: string; // Type of chart.
     @Input() legend?: ChartLegendOptions; // Legend options.
     @Input() height = 300; // Height of the chart element.
-    @Input() filter?: boolean | string; // Whether to filter labels. If not defined, true if contextLevel and instanceId are set.
+    @Input({ transform: toBoolean }) filter?: boolean; // Whether to filter labels.
+                                                       // If not defined, true if contextLevel and instanceId are set.
     @Input() contextLevel?: ContextLevel; // The context level of the text.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters.
-    @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the labels for some reason.
+    @Input({ transform: toBoolean }) wsNotFiltered = false; // If true it means the WS didn't filter the labels for some reason.
     @ViewChild('canvas') canvas?: ElementRef<HTMLCanvasElement>;
 
     chart?: ChartWithLegend;
@@ -158,7 +159,7 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges {
             clean: true,
             singleLine: true,
             courseId: this.courseId,
-            wsNotFiltered: CoreUtils.isTrueOrOne(this.wsNotFiltered),
+            wsNotFiltered: this.wsNotFiltered,
         };
 
         const filters = await CoreFilterHelper.getFilters(this.contextLevel, this.contextInstanceId, options);
diff --git a/src/core/components/chrono/chrono.ts b/src/core/components/chrono/chrono.ts
index 0604cc65e..18823991d 100644
--- a/src/core/components/chrono/chrono.ts
+++ b/src/core/components/chrono/chrono.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import {
     Component,
     Input,
@@ -42,11 +43,11 @@ import {
 })
 export class CoreChronoComponent implements OnInit, OnChanges, OnDestroy {
 
-    @Input() running?: boolean; // Set it to true to start the chrono. Set it to false to stop it.
+    @Input({ transform: toBoolean }) running = false; // Set it to true to start the chrono. Set it to false to stop it.
     @Input() startTime = 0; // Number of milliseconds to put in the chrono before starting.
     @Input() endTime?: number; // Number of milliseconds to stop the chrono.
-    @Input() reset?: boolean; // Set it to true to reset the chrono.
-    @Input() hours = true;
+    @Input({ transform: toBoolean }) reset = false; // Set it to true to reset the chrono.
+    @Input({ transform: toBoolean }) hours = true;
     @Output() onEnd: EventEmitter<void>; // Will emit an event when the endTime is reached.
 
     time = 0;
diff --git a/src/core/components/combobox/combobox.ts b/src/core/components/combobox/combobox.ts
index e85060584..51a8ee8a3 100644
--- a/src/core/components/combobox/combobox.ts
+++ b/src/core/components/combobox/combobox.ts
@@ -17,6 +17,7 @@ import { Translate } from '@singletons';
 import { ModalOptions } from '@ionic/core';
 import { CoreModals } from '@services/modals';
 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that show a combo select button (combobox).
@@ -52,7 +53,7 @@ export class CoreComboboxComponent implements ControlValueAccessor {
 
     @Input() interface: 'popover' | 'modal' = 'popover';
     @Input() label = Translate.instant('core.show'); // Aria label.
-    @Input() disabled = false;
+    @Input({ transform: toBoolean }) disabled = false;
     @Input() selection = '';
     @Output() onChange = new EventEmitter<unknown>(); // Will emit an event the value changed.
 
diff --git a/src/core/components/context-menu/context-menu-item.ts b/src/core/components/context-menu/context-menu-item.ts
index 25a2ab4d4..3008fc82d 100644
--- a/src/core/components/context-menu/context-menu-item.ts
+++ b/src/core/components/context-menu/context-menu-item.ts
@@ -14,6 +14,7 @@
 
 import { Component, Input, Output, OnInit, OnDestroy, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
 import { CoreContextMenuComponent } from '../context-menu/context-menu';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * This directive adds a item to the Context Menu popover.
@@ -40,19 +41,19 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
     // If is "toggle" a toggle switch will be shown.
     // If no icon or spinner is selected, no action or link will work.
     // If href but no iconAction is provided arrow-right will be used.
-    @Input() iconSlash?: boolean; // Display a red slash over the icon.
+    @Input({ transform: toBoolean }) iconSlash = false; // Display a red slash over the icon.
     @Input() ariaAction?: string; // Aria label to add to iconAction. If not set, it will be equal to content.
     @Input() href?: string; // Link to go if no action provided.
-    @Input() captureLink?: boolean | string; // Whether the link needs to be captured by the app.
-    @Input() autoLogin: boolean | string = true; // Whether the link needs to be opened using auto-login.
-    @Input() closeOnClick = true; // Whether to close the popover when the item is clicked.
+    @Input({ transform: toBoolean }) captureLink = false; // Whether the link needs to be captured by the app.
+    @Input({ transform: toBoolean }) autoLogin = true; // Whether the link needs to be opened using auto-login.
+    @Input({ transform: toBoolean }) closeOnClick = true; // Whether to close the popover when the item is clicked.
     @Input() priority?: number; // Used to sort items. The highest priority, the highest position.
     @Input() badge?: string; // A badge to show in the item.
     @Input() badgeClass?: number; // A class to set in the badge.
     @Input() badgeA11yText?: string; // Description for the badge, if needed.
-    @Input() hidden?: boolean; // Whether the item should be hidden.
-    @Input() showBrowserWarning = true; // Whether to show a warning before opening browser (for links). Defaults to true.
-    @Input() toggle = false; // Whether the toggle is on or off.
+    @Input({ transform: toBoolean }) hidden = false; // Whether the item should be hidden.
+    @Input({ transform: toBoolean }) showBrowserWarning = true; // Whether to show a warning before opening browser (for links).
+    @Input({ transform: toBoolean }) toggle = false; // Whether the toggle is on or off.
     @Output() action?: EventEmitter<() => void>; // Will emit an event when the item clicked.
     @Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked.
     @Output() toggleChange = new EventEmitter<boolean>();// Will emit an event when toggle changes to enable 2-way data binding.
@@ -94,10 +95,6 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
      * @param event Event.
      */
     toggleChanged(event: Event): void {
-        if (this.toggle === undefined) {
-            return;
-        }
-
         event.preventDefault();
         event.stopPropagation();
         this.toggleChange.emit(this.toggle);
diff --git a/src/core/components/course-image/course-image.html b/src/core/components/course-image/course-image.html
index fee483a9d..adc91885c 100644
--- a/src/core/components/course-image/course-image.html
+++ b/src/core/components/course-image/course-image.html
@@ -1,4 +1,4 @@
-<ion-icon *ngIf="!course.courseimage" name="fas-graduation-cap" slot="start" aria-hidden="true" />
-<ion-avatar *ngIf="course.courseimage" slot="start">
-    <img [url]="course.courseimage" core-external-content alt="" (error)="loadFallbackCourseIcon()" />
+<ion-icon *ngIf="!course().courseimage" name="fas-graduation-cap" slot="start" aria-hidden="true" />
+<ion-avatar *ngIf="course().courseimage" slot="start">
+    <img [url]="course().courseimage" core-external-content alt="" (error)="loadFallbackCourseIcon()" />
 </ion-avatar>
diff --git a/src/core/components/course-image/course-image.ts b/src/core/components/course-image/course-image.ts
index dc440b81a..a6b559873 100644
--- a/src/core/components/course-image/course-image.ts
+++ b/src/core/components/course-image/course-image.ts
@@ -12,7 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import { Component, Input, ElementRef, OnInit, OnChanges, HostBinding } from '@angular/core';
+import { toBoolean } from '@/core/transforms/boolean';
+import { Component, ElementRef, HostBinding, input, effect } from '@angular/core';
 import { CoreCourseListItem } from '@features/courses/services/courses';
 import { CoreCoursesHelper } from '@features/courses/services/courses-helper';
 import { CoreColors } from '@singletons/colors';
@@ -22,41 +23,31 @@ import { CoreColors } from '@singletons/colors';
     templateUrl: 'course-image.html',
     styleUrls: ['./course-image.scss'],
 })
-export class CoreCourseImageComponent implements OnInit, OnChanges {
+export class CoreCourseImageComponent {
 
-    @Input() course!: CoreCourseListItem;
-    @Input() fill = false;
+    course = input.required<CoreCourseListItem>();
+    fill = input(false, { transform: toBoolean });
 
     protected element: HTMLElement;
 
     constructor(element: ElementRef) {
         this.element = element.nativeElement;
+
+        effect(() => {
+            this.setCourseColor();
+        });
     }
 
     @HostBinding('class.fill-container')
     get fillContainer(): boolean {
-        return this.fill;
-    }
-
-    /**
-     * @inheritdoc
-     */
-    ngOnInit(): void {
-        this.setCourseColor();
-    }
-
-    /**
-     * @inheritdoc
-     */
-    ngOnChanges(): void {
-        this.setCourseColor();
+        return this.fill();
     }
 
     /**
      * Removes the course image set because it cannot be loaded and set the fallback icon color.
      */
     loadFallbackCourseIcon(): void {
-        this.course.courseimage = undefined;
+        this.course().courseimage = undefined;
 
         // Set the color because it won't be set at this point.
         this.setCourseColor();
@@ -66,15 +57,17 @@ export class CoreCourseImageComponent implements OnInit, OnChanges {
      * Set course color.
      */
     protected async setCourseColor(): Promise<void> {
-        await CoreCoursesHelper.loadCourseColorAndImage(this.course);
+        const course = this.course();
 
-        if (this.course.color) {
-            this.element.style.setProperty('--course-color', this.course.color);
+        await CoreCoursesHelper.loadCourseColorAndImage(course);
 
-            const tint = CoreColors.lighter(this.course.color, 50);
+        if (course.color) {
+            this.element.style.setProperty('--course-color', course.color);
+
+            const tint = CoreColors.lighter(course.color, 50);
             this.element.style.setProperty('--course-color-tint', tint);
-        } else if(this.course.colorNumber !== undefined) {
-            this.element.classList.add('course-color-' + this.course.colorNumber);
+        } else if(course.colorNumber !== undefined) {
+            this.element.classList.add('course-color-' + course.colorNumber);
         }
     }
 
diff --git a/src/core/components/download-refresh/download-refresh.ts b/src/core/components/download-refresh/download-refresh.ts
index 31ae04256..a4f491726 100644
--- a/src/core/components/download-refresh/download-refresh.ts
+++ b/src/core/components/download-refresh/download-refresh.ts
@@ -15,6 +15,7 @@
 import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
 import { DownloadStatus } from '@/core/constants';
 import { CoreAnimations } from '@components/animations';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to show a download button with refresh option, the spinner and the status of it.
@@ -34,9 +35,9 @@ export class CoreDownloadRefreshComponent implements OnInit {
     @Input() status?: DownloadStatus; // Download status.
     @Input() statusesTranslatable?: Partial<CoreDownloadStatusTranslatable>; // Download statuses translatable strings.
     @Input() statusSubject = ''; // Status subject to use on name filed in the translatable string.
-    @Input() enabled = false; // Whether the download is enabled.
-    @Input() loading = true; // Force loading status when is not downloading.
-    @Input() canTrustDownload = false; // If false, refresh will be shown if downloaded.
+    @Input({ transform: toBoolean }) enabled = false; // Whether the download is enabled.
+    @Input({ transform: toBoolean }) loading = true; // Force loading status when is not downloading.
+    @Input({ transform: toBoolean }) canTrustDownload = false; // If false, refresh will be shown if downloaded.
     @Output() action: EventEmitter<boolean>; // Will emit an event when the item clicked.
 
     /**
diff --git a/src/core/components/empty-box/empty-box.ts b/src/core/components/empty-box/empty-box.ts
index 1507ab73f..55006152f 100644
--- a/src/core/components/empty-box/empty-box.ts
+++ b/src/core/components/empty-box/empty-box.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, HostBinding, Input } from '@angular/core';
 
 /**
@@ -30,13 +31,13 @@ import { Component, HostBinding, Input } from '@angular/core';
 export class CoreEmptyBoxComponent {
 
     @Input() message = ''; // Message to display.
-    @Input() dimmed = false; // Wether the box is dimmed or not.
+    @Input({ transform: toBoolean }) dimmed = false; // Wether the box is dimmed or not.
     @Input() icon?: string; // Name of the icon to use.
     @Input() image?: string; // Image source. If an icon is provided, image won't be used.
     /**
      * @deprecated since 4.4. Not used anymore.
      */
-    @Input() flipIconRtl = false;
+    @Input({ transform: toBoolean }) flipIconRtl = false;
 
     @HostBinding('class.dimmed')
     get isDimmed(): boolean {
diff --git a/src/core/components/file/file.ts b/src/core/components/file/file.ts
index a525a5ec2..e42544666 100644
--- a/src/core/components/file/file.ts
+++ b/src/core/components/file/file.ts
@@ -27,6 +27,7 @@ import { DownloadStatus } from '@/core/constants';
 import { CoreEventObserver, CoreEvents } from '@singletons/events';
 import { CoreWSFile } from '@services/ws';
 import { CorePlatform } from '@services/platform';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button
@@ -41,11 +42,11 @@ export class CoreFileComponent implements OnInit, OnDestroy {
     @Input() file?: CoreWSFile; // The file.
     @Input() component?: string; // Component the file belongs to.
     @Input() componentId?: string | number; // Component ID.
-    @Input() canDelete?: boolean | string; // Whether file can be deleted.
-    @Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded.
-    @Input() canDownload?: boolean | string = true; // Whether file can be downloaded.
-    @Input() showSize?: boolean | string = true; // Whether show filesize.
-    @Input() showTime?: boolean | string = true; // Whether show file time modified.
+    @Input({ transform: toBoolean }) canDelete = false; // Whether file can be deleted.
+    @Input({ transform: toBoolean }) alwaysDownload = false; // True to always display the refresh button when file is downloaded.
+    @Input({ transform: toBoolean }) canDownload = true; // Whether file can be downloaded.
+    @Input({ transform: toBoolean }) showSize = true; // Whether show filesize.
+    @Input({ transform: toBoolean }) showTime = true; // Whether show file time modified.
     @Output() onDelete: EventEmitter<void>; // Will notify when the delete button is clicked.
 
     isDownloading?: boolean;
@@ -77,10 +78,6 @@ export class CoreFileComponent implements OnInit, OnDestroy {
             return;
         }
 
-        this.canDelete = CoreUtils.isTrueOrOne(this.canDelete);
-        this.alwaysDownload = CoreUtils.isTrueOrOne(this.alwaysDownload);
-        this.canDownload = CoreUtils.isTrueOrOne(this.canDownload);
-
         this.fileUrl = CoreFileHelper.getFileUrl(this.file);
         this.timemodified = this.file.timemodified || 0;
         this.siteId = CoreSites.getCurrentSiteId();
@@ -92,11 +89,11 @@ export class CoreFileComponent implements OnInit, OnDestroy {
         this.openButtonIcon = this.defaultIsOpenWithPicker ? 'fas-file' : 'fas-share-from-square';
         this.openButtonLabel = this.defaultIsOpenWithPicker ? 'core.openfile' : 'core.openwith';
 
-        if (CoreUtils.isTrueOrOne(this.showSize) && this.fileSize && this.fileSize >= 0) {
+        if (this.showSize && this.fileSize && this.fileSize >= 0) {
             this.fileSizeReadable = CoreTextUtils.bytesToSize(this.fileSize, 2);
         }
 
-        this.showTime = CoreUtils.isTrueOrOne(this.showTime) && this.timemodified > 0;
+        this.showTime = this.showTime && this.timemodified > 0;
 
         if ('isexternalfile' in this.file && this.file.isexternalfile) {
             this.alwaysDownload = true; // Always show the download button in external files.
diff --git a/src/core/components/files/files.ts b/src/core/components/files/files.ts
index 7aefe9640..f76da07d6 100644
--- a/src/core/components/files/files.ts
+++ b/src/core/components/files/files.ts
@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, OnInit, DoCheck, KeyValueDiffers } from '@angular/core';
 import { CoreFileEntry } from '@services/file-helper';
 
 import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreUtils } from '@services/utils/utils';
 
 /**
  * Component to render a file list.
@@ -33,11 +33,11 @@ export class CoreFilesComponent implements OnInit, DoCheck {
     @Input() files: CoreFileEntry[] = []; // List of files.
     @Input() component?: string; // Component the downloaded files will be linked to.
     @Input() componentId?: string | number; // Component ID.
-    @Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded.
-    @Input() canDownload?: boolean | string = true; // Whether file can be downloaded.
-    @Input() showSize?: boolean | string = true; // Whether show filesize.
-    @Input() showTime?: boolean | string = true; // Whether show file time modified.
-    @Input() showInline = false; // If true, it will reorder and try to show inline files first.
+    @Input({ transform: toBoolean }) alwaysDownload = false; // True to always display the refresh button when file is downloaded.
+    @Input({ transform: toBoolean }) canDownload = true; // Whether file can be downloaded.
+    @Input({ transform: toBoolean }) showSize = true; // Whether show filesize.
+    @Input({ transform: toBoolean }) showTime = true; // Whether show file time modified.
+    @Input({ transform: toBoolean }) showInline = false; // If true, it will reorder and try to show inline files first.
     @Input() extraHtml?: string[]; // Extra HTML for each attachment. Each HTML should be at the same position as the attachment.
 
     contentText?: string;
@@ -53,7 +53,7 @@ export class CoreFilesComponent implements OnInit, DoCheck {
      * @inheritdoc
      */
     ngOnInit(): void {
-        if (CoreUtils.isTrueOrOne(this.showInline) && this.files) {
+        if (this.showInline && this.files) {
             this.renderInlineFiles();
         }
     }
@@ -62,7 +62,7 @@ export class CoreFilesComponent implements OnInit, DoCheck {
      * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays).
      */
     ngDoCheck(): void {
-        if (CoreUtils.isTrueOrOne(this.showInline) && this.files) {
+        if (this.showInline && this.files) {
             // Check if there's any change in the files array.
             const changes = this.differ.diff(this.files);
             if (changes) {
diff --git a/src/core/components/group-selector/group-selector.ts b/src/core/components/group-selector/group-selector.ts
index 41dbcfe29..df9139a34 100644
--- a/src/core/components/group-selector/group-selector.ts
+++ b/src/core/components/group-selector/group-selector.ts
@@ -34,7 +34,7 @@ export class CoreGroupSelectorComponent {
 
     @Input() groupInfo?: CoreGroupInfo;
     @Input() multipleGroupsMessage?: string;
-    @Input() selected!: number;
+    @Input({ required: true }) selected!: number;
     @Input() courseId?: number;
     @Output() selectedChange = new EventEmitter<number>();
 
diff --git a/src/core/components/iframe/iframe.ts b/src/core/components/iframe/iframe.ts
index 5012f9128..1fca966a9 100644
--- a/src/core/components/iframe/iframe.ts
+++ b/src/core/components/iframe/iframe.ts
@@ -21,7 +21,6 @@ import { CoreFile } from '@services/file';
 import { CoreDomUtils } from '@services/utils/dom';
 import { CoreUrl } from '@singletons/url';
 import { CoreIframeUtils } from '@services/utils/iframe';
-import { CoreUtils } from '@services/utils/utils';
 import { DomSanitizer, Router, StatusBar } from '@singletons';
 import { CoreEventObserver, CoreEvents } from '@singletons/events';
 import { CoreScreen, CoreScreenOrientation } from '@services/screen';
@@ -29,6 +28,7 @@ import { Subscription } from 'rxjs';
 import { filter } from 'rxjs/operators';
 import { NavigationStart } from '@angular/router';
 import { CoreSites } from '@services/sites';
+import { toBoolean } from '@/core/transforms/boolean';
 
 @Component({
     selector: 'core-iframe',
@@ -49,10 +49,10 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
     @Input() id: string | null = null;
     @Input() iframeWidth = '100%';
     @Input() iframeHeight = '100%';
-    @Input() allowFullscreen?: boolean | string;
-    @Input() showFullscreenOnToolbar?: boolean | string;
-    @Input() autoFullscreenOnRotate?: boolean | string;
-    @Input() allowAutoLogin = true;
+    @Input({ transform: toBoolean }) allowFullscreen = false;
+    @Input({ transform: toBoolean }) showFullscreenOnToolbar = false;
+    @Input({ transform: toBoolean }) autoFullscreenOnRotate = false;
+    @Input({ transform: toBoolean }) allowAutoLogin = true;
     @Output() loaded: EventEmitter<HTMLIFrameElement> = new EventEmitter<HTMLIFrameElement>();
 
     loading?: boolean;
@@ -167,15 +167,6 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
         if (changes.iframeHeight) {
             this.iframeHeight = (this.iframeHeight && CoreDomUtils.formatPixelsSize(this.iframeHeight)) || '100%';
         }
-        if (changes.allowFullscreen) {
-            this.allowFullscreen = CoreUtils.isTrueOrOne(this.allowFullscreen);
-        }
-        if (changes.showFullscreenOnToolbar) {
-            this.showFullscreenOnToolbar = CoreUtils.isTrueOrOne(this.showFullscreenOnToolbar);
-        }
-        if (changes.autoFullscreenOnRotate) {
-            this.autoFullscreenOnRotate = CoreUtils.isTrueOrOne(this.autoFullscreenOnRotate);
-        }
 
         if (!changes.src) {
             return;
diff --git a/src/core/components/infinite-loading/infinite-loading.ts b/src/core/components/infinite-loading/infinite-loading.ts
index 49103a3df..2959281c1 100644
--- a/src/core/components/infinite-loading/infinite-loading.ts
+++ b/src/core/components/infinite-loading/infinite-loading.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange, ViewChild, ElementRef } from '@angular/core';
 import { IonInfiniteScroll } from '@ionic/angular';
 import { CoreWait } from '@singletons/wait';
@@ -30,8 +31,8 @@ const THRESHOLD = .15; // % of the scroll element height that must be close to t
 })
 export class CoreInfiniteLoadingComponent implements OnChanges {
 
-    @Input() enabled!: boolean;
-    @Input() error = false;
+    @Input({ required: true, transform: toBoolean }) enabled = false;
+    @Input({ transform: toBoolean }) error = false;
     @Input() position: 'top' | 'bottom' = 'bottom';
     @Output() action: EventEmitter<() => void>; // Will emit an event when triggered.
 
diff --git a/src/core/components/loading/loading.ts b/src/core/components/loading/loading.ts
index f890c40ee..bba7bb873 100644
--- a/src/core/components/loading/loading.ts
+++ b/src/core/components/loading/loading.ts
@@ -22,6 +22,7 @@ import { CorePromisedValue } from '@classes/promised-value';
 import { AsyncDirective } from '@classes/async-directive';
 import { CorePlatform } from '@services/platform';
 import { CoreWait } from '@singletons/wait';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to show a loading spinner and message while data is being loaded.
@@ -51,9 +52,9 @@ import { CoreWait } from '@singletons/wait';
 })
 export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit, AsyncDirective, OnDestroy {
 
-    @Input() hideUntil: unknown = false; // Determine when should the contents be shown.
+    @Input({ transform: toBoolean }) hideUntil = false; // Determine when should the contents be shown.
     @Input() message?: string; // Message to show while loading.
-    @Input() fullscreen = true; // Use the whole screen.
+    @Input({ transform: toBoolean }) fullscreen = true; // Use the whole screen.
 
     uniqueId: string;
     loaded = false;
@@ -108,7 +109,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit, A
      * @inheritdoc
      */
     ngAfterViewInit(): void {
-        this.changeState(!!this.hideUntil);
+        this.changeState(this.hideUntil);
     }
 
     /**
@@ -116,7 +117,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit, A
      */
     ngOnChanges(changes: { [name: string]: SimpleChange }): void {
         if (changes.hideUntil) {
-            this.changeState(!!this.hideUntil);
+            this.changeState(this.hideUntil);
         }
     }
 
diff --git a/src/core/components/local-file/local-file.ts b/src/core/components/local-file/local-file.ts
index cddbd6a72..0c4b12949 100644
--- a/src/core/components/local-file/local-file.ts
+++ b/src/core/components/local-file/local-file.ts
@@ -27,6 +27,7 @@ import { CoreUtils, CoreUtilsOpenFileOptions, OpenFileAction } from '@services/u
 import { CoreForms } from '@singletons/form';
 import { CorePath } from '@singletons/path';
 import { CorePlatform } from '@services/platform';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to handle a local file. Only files inside the app folder can be managed.
@@ -41,8 +42,8 @@ import { CorePlatform } from '@services/platform';
 export class CoreLocalFileComponent implements OnInit {
 
     @Input() file?: FileEntry; // A fileEntry retrieved using CoreFileProvider.getFile or similar.
-    @Input() manage?: boolean | string; // Whether the user can manage the file (edit and delete).
-    @Input() overrideClick?: boolean | string; // Whether the default item click should be overridden.
+    @Input({ transform: toBoolean }) manage = false; // Whether the user can manage the file (edit and delete).
+    @Input({ transform: toBoolean }) overrideClick = false; // Whether the default item click should be overridden.
     @Output() onDelete = new EventEmitter<void>(); // Will notify when the file is deleted.
     @Output() onRename = new EventEmitter<{ file: FileEntry }>(); // Will notify when the file is renamed.
     @Output() onClick = new EventEmitter<void>(); // Will notify when the file is clicked. Only if overrideClick is true.
@@ -67,8 +68,6 @@ export class CoreLocalFileComponent implements OnInit {
      * @inheritdoc
      */
     async ngOnInit(): Promise<void> {
-        this.manage = CoreUtils.isTrueOrOne(this.manage);
-
         if (!this.file) {
             return;
         }
@@ -119,7 +118,7 @@ export class CoreLocalFileComponent implements OnInit {
         e.preventDefault();
         e.stopPropagation();
 
-        if (!isOpenButton && CoreUtils.isTrueOrOne(this.overrideClick) && this.onClick.observed) {
+        if (!isOpenButton && this.overrideClick && this.onClick.observed) {
             this.onClick.emit();
 
             return;
diff --git a/src/core/components/mark-required/mark-required.ts b/src/core/components/mark-required/mark-required.ts
index f0bc34b51..e6ed33d3e 100644
--- a/src/core/components/mark-required/mark-required.ts
+++ b/src/core/components/mark-required/mark-required.ts
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import { Component, Input, OnInit, AfterViewInit, ElementRef } from '@angular/core';
+import { toBoolean } from '@/core/transforms/boolean';
+import { Component, Input, AfterViewInit, ElementRef } from '@angular/core';
 
 import { CoreTextUtils } from '@services/utils/text';
-import { CoreUtils } from '@services/utils/utils';
 import { Translate } from '@singletons';
 
 /**
@@ -33,9 +33,9 @@ import { Translate } from '@singletons';
     templateUrl: 'core-mark-required.html',
     styleUrls: ['mark-required.scss'],
 })
-export class CoreMarkRequiredComponent implements OnInit, AfterViewInit {
+export class CoreMarkRequiredComponent implements AfterViewInit {
 
-    @Input('core-mark-required') coreMarkRequired: boolean | string = true;
+    @Input({ alias: 'core-mark-required', transform: toBoolean }) coreMarkRequired = true;
 
     protected hostElement: HTMLElement;
     requiredLabel = Translate.instant('core.required');
@@ -46,13 +46,6 @@ export class CoreMarkRequiredComponent implements OnInit, AfterViewInit {
         this.hostElement = element.nativeElement;
     }
 
-    /**
-     * @inheritdoc
-     */
-    ngOnInit(): void {
-        this.coreMarkRequired = CoreUtils.isTrueOrOne(this.coreMarkRequired);
-    }
-
     /**
      * @inheritdoc
      */
diff --git a/src/core/components/message/message.ts b/src/core/components/message/message.ts
index d9d1a0572..b8982f723 100644
--- a/src/core/components/message/message.ts
+++ b/src/core/components/message/message.ts
@@ -19,6 +19,7 @@ import { CoreSites } from '@services/sites';
 import { CoreText } from '@singletons/text';
 import { CoreTextUtils } from '@services/utils/text';
 import { CoreUserWithAvatar } from '@components/user-avatar/user-avatar';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to handle a message in a conversation.
@@ -39,7 +40,7 @@ export class CoreMessageComponent implements OnInit {
     @Input() instanceId = 0;
     @Input() courseId?: number;
     @Input() contextLevel: ContextLevel = ContextLevel.SYSTEM;
-    @Input() showDelete = false;
+    @Input({ transform: toBoolean }) showDelete = false;
     @Output() onDeleteMessage = new EventEmitter<void>();
     @Output() onUndoDeleteMessage = new EventEmitter<void>();
     @Output() afterRender = new EventEmitter<void>();
diff --git a/src/core/components/mod-icon/mod-icon.ts b/src/core/components/mod-icon/mod-icon.ts
index 7e1a26cb8..ab8727dfe 100644
--- a/src/core/components/mod-icon/mod-icon.ts
+++ b/src/core/components/mod-icon/mod-icon.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { CoreConstants, ModPurpose } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 import {
     ChangeDetectionStrategy,
     Component,
@@ -54,10 +55,10 @@ export class CoreModIconComponent implements OnInit, OnChanges {
     @Input() fallbackTranslation = ''; // Fallback translation string if cannot auto translate.
     @Input() componentId?: number; // Component Id for external icons.
     @Input() modicon?: string; // Module icon url or local url.
-    @Input() showAlt = true; // Show alt otherwise it's only presentation icon.
+    @Input({ transform: toBoolean }) showAlt = true; // Show alt otherwise it's only presentation icon.
     @Input() purpose: ModPurpose = ModPurpose.MOD_PURPOSE_OTHER; // Purpose of the module.
-    @Input() @HostBinding('class.colorize') colorize = true; // Colorize the icon. Only applies on 4.0 onwards.
-    @Input() isBranded?: boolean; // If icon is branded and no colorize will be applied.
+    @Input({ transform: toBoolean }) @HostBinding('class.colorize') colorize = true; // Colorize the icon. Only applies on 4.0+.
+    @Input({ transform: toBoolean }) isBranded = false; // If icon is branded and no colorize will be applied.
 
     @HostBinding('class.branded') brandedClass?: boolean;
 
diff --git a/src/core/components/progress-bar/progress-bar.ts b/src/core/components/progress-bar/progress-bar.ts
index 932e63ddc..1d30c770c 100644
--- a/src/core/components/progress-bar/progress-bar.ts
+++ b/src/core/components/progress-bar/progress-bar.ts
@@ -30,7 +30,7 @@ import { DomSanitizer, Translate } from '@singletons';
 })
 export class CoreProgressBarComponent implements OnChanges {
 
-    @Input() progress!: number | string; // Percentage from 0 to 100. Negative number will show an indeterminate progress bar.
+    @Input({ required: true }) progress!: number | string; // Percentage (0 to 100). Negative number will show an indeterminate bar.
     @Input() text?: string; // Percentage in text to be shown at the right. If not defined, progress will be used.
     @Input() a11yText?: string; // Accessibility text to read before the percentage.
     @Input() ariaDescribedBy?: string; // ID of the element that described the progress, if any.
diff --git a/src/core/components/recaptcha/recaptcha.ts b/src/core/components/recaptcha/recaptcha.ts
index ef4608f4a..f37662f3c 100644
--- a/src/core/components/recaptcha/recaptcha.ts
+++ b/src/core/components/recaptcha/recaptcha.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, OnInit } from '@angular/core';
 
 import { CoreLang, CoreLangFormat } from '@services/lang';
@@ -32,7 +33,7 @@ export class CoreRecaptchaComponent implements OnInit {
     @Input() publicKey?: string; // The site public key.
     @Input() modelValueName = 'recaptcharesponse'; // Name of the model property where to store the response.
     @Input() siteUrl = ''; // The site URL. If not defined, current site.
-    @Input() showRequiredError = false; // Whether to display the required error if recaptcha hasn't been answered.
+    @Input({ transform: toBoolean }) showRequiredError = false; // Whether to display the required error if recaptcha not answered.
 
     expired = false;
 
diff --git a/src/core/components/send-message-form/send-message-form.ts b/src/core/components/send-message-form/send-message-form.ts
index fe624981c..8f0a4be5b 100644
--- a/src/core/components/send-message-form/send-message-form.ts
+++ b/src/core/components/send-message-form/send-message-form.ts
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ElementRef } from '@angular/core';
+import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
 import { CoreConfig } from '@services/config';
 import { CoreEvents } from '@singletons/events';
 import { CoreSites } from '@services/sites';
-import { CoreUtils } from '@services/utils/utils';
 import { CoreTextUtils } from '@services/utils/text';
 import { CoreConstants } from '@/core/constants';
 import { CoreForms } from '@singletons/form';
 import { CorePlatform } from '@services/platform';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display a "send message form".
@@ -37,12 +37,12 @@ import { CorePlatform } from '@services/platform';
     templateUrl: 'core-send-message-form.html',
     styleUrls: ['send-message-form.scss'],
 })
-export class CoreSendMessageFormComponent implements OnInit {
+export class CoreSendMessageFormComponent {
 
     @Input() message = ''; // Input text.
     @Input() placeholder = ''; // Placeholder for the input area.
-    @Input() showKeyboard = false; // If keyboard is shown or not.
-    @Input() sendDisabled = false; // If send is disabled.
+    @Input({ transform: toBoolean }) showKeyboard = false; // If keyboard is shown or not.
+    @Input({ transform: toBoolean }) sendDisabled = false; // If send is disabled.
     @Output() onSubmit: EventEmitter<string>; // Send data when submitting the message form.
     @Output() onResize: EventEmitter<void>; // Emit when resizing the textarea.
 
@@ -68,10 +68,6 @@ export class CoreSendMessageFormComponent implements OnInit {
         }, CoreSites.getCurrentSiteId());
     }
 
-    ngOnInit(): void {
-        this.showKeyboard = CoreUtils.isTrueOrOne(this.showKeyboard);
-    }
-
     /**
      * Form submitted.
      *
diff --git a/src/core/components/sheet-modal/sheet-modal.ts b/src/core/components/sheet-modal/sheet-modal.ts
index e93409c7c..af8ce0f17 100644
--- a/src/core/components/sheet-modal/sheet-modal.ts
+++ b/src/core/components/sheet-modal/sheet-modal.ts
@@ -28,7 +28,7 @@ import { CoreDirectivesRegistry } from '@singletons/directives-registry';
 })
 export class CoreSheetModalComponent<T extends CoreModalComponent> implements AfterViewInit {
 
-    @Input() component!: Constructor<T>;
+    @Input({ required: true }) component!: Constructor<T>;
     @Input() componentProps?: Record<string, unknown>;
     @ViewChild('wrapper') wrapper?: ElementRef<HTMLElement>;
 
diff --git a/src/core/components/sites-list/sites-list.html b/src/core/components/sites-list/sites-list.html
index 429820659..890c48764 100644
--- a/src/core/components/sites-list/sites-list.html
+++ b/src/core/components/sites-list/sites-list.html
@@ -27,7 +27,7 @@
                 <core-format-text [text]="site.siteName" clean="true" [siteId]="site.id" />
             </h2>
             <p *ngIf="displaySiteUrl(site)">
-                <a [href]="site.siteUrl" core-link [autoLogin]="isCurrentSite ? 'yes' : 'no'">
+                <a [href]="site.siteUrl" core-link [autoLogin]="!!isCurrentSite">
                     {{ site.siteUrlWithoutProtocol }}
                 </a>
             </p>
diff --git a/src/core/components/sites-list/sites-list.ts b/src/core/components/sites-list/sites-list.ts
index 0edb15b4f..78d83c89f 100644
--- a/src/core/components/sites-list/sites-list.ts
+++ b/src/core/components/sites-list/sites-list.ts
@@ -17,6 +17,7 @@ import { Component, ContentChild, Input, Output, TemplateRef, EventEmitter } fro
 import { CoreSiteBasicInfo } from '@services/sites';
 import { CoreAccountsList } from '@features/login/services/login-helper';
 import { CoreSitesFactory } from '@services/sites-factory';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display a list of sites (accounts).
@@ -42,9 +43,9 @@ import { CoreSitesFactory } from '@services/sites-factory';
 })
 export class CoreSitesListComponent<T extends CoreSiteBasicInfo> {
 
-    @Input() accountsList!: CoreAccountsList<T>;
-    @Input() sitesClickable = false; // Whether the sites are clickable.
-    @Input() currentSiteClickable?: boolean; // If set, specify a different clickable value for current site.
+    @Input({ required: true }) accountsList!: CoreAccountsList<T>;
+    @Input({ transform: toBoolean }) sitesClickable = false; // Whether the sites are clickable.
+    @Input({ transform: toBoolean }) currentSiteClickable = false; // If set, specify a different clickable value for current site.
     @Output() onSiteClicked = new EventEmitter<T>();
 
     @ContentChild('siteItem') siteItemTemplate?: TemplateRef<{site: T; isCurrentSite: boolean}>;
diff --git a/src/core/components/tabs/tab.ts b/src/core/components/tabs/tab.ts
index dd276f647..a7294e67f 100644
--- a/src/core/components/tabs/tab.ts
+++ b/src/core/components/tabs/tab.ts
@@ -45,7 +45,7 @@ import { CoreTabsComponent } from './tabs';
 })
 export class CoreTabComponent implements OnInit, OnDestroy, CoreTabBase {
 
-    @Input() title!: string; // The tab title.
+    @Input({ required: true }) title!: string; // The tab title.
     @Input() icon?: string; // The tab icon.
     @Input() badge?: string; // A badge to add in the tab.
     @Input() badgeStyle?: string; // The badge color.
diff --git a/src/core/components/tabs/tabs.ts b/src/core/components/tabs/tabs.ts
index ae8782a56..a84bf2e4d 100644
--- a/src/core/components/tabs/tabs.ts
+++ b/src/core/components/tabs/tabs.ts
@@ -22,6 +22,7 @@ import {
 
 import { CoreTabsBaseComponent } from '@classes/tabs';
 import { CoreTabComponent } from './tab';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * This component displays some top scrollable tabs that will autohide on vertical scroll.
@@ -44,7 +45,7 @@ import { CoreTabComponent } from './tab';
 })
 export class CoreTabsComponent extends CoreTabsBaseComponent<CoreTabComponent> implements AfterViewInit {
 
-    @Input() parentScrollable = false; // Determine if the scroll should be in the parent content or the tab itself.
+    @Input({ transform: toBoolean }) parentScrollable = false; // Determine if scroll should be in the parent content or the tab.
     @Input() layout: 'icon-top' | 'icon-start' | 'icon-end' | 'icon-bottom' | 'icon-hide' | 'label-hide' = 'icon-hide';
 
     @ViewChild('originalTabs')
diff --git a/src/core/components/timer/timer.ts b/src/core/components/timer/timer.ts
index eefe50a37..15ba62e7b 100644
--- a/src/core/components/timer/timer.ts
+++ b/src/core/components/timer/timer.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ElementRef } from '@angular/core';
 import { CoreUser } from '@features/user/services/user';
 
@@ -35,7 +36,7 @@ export class CoreTimerComponent implements OnInit, OnDestroy {
     @Input() timeLeftClass?: string; // Name of the class to apply with each second. By default, 'core-timer-timeleft-'.
     @Input() timeLeftClassThreshold = 100; // Number of seconds to start adding the timeLeftClass. Set it to -1 to not add it.
     @Input() align = 'start'; // Where to align the time and text. Defaults to 'start'. Other values: 'center', 'end'.
-    @Input() hidable = false; // Whether the user can hide the time left.
+    @Input({ transform: toBoolean }) hidable = false; // Whether the user can hide the time left.
     @Input() timeUpText?: string; // Text to show when the timer reaches 0. If not defined, 'core.timesup'.
     @Input() mode: CoreTimerMode = CoreTimerMode.ITEM; // How to display data.
     @Input() underTimeClassThresholds = []; // Number of seconds to add the class 'core-timer-under-'.
@@ -45,7 +46,7 @@ export class CoreTimerComponent implements OnInit, OnDestroy {
     /**
      * @deprecated since 4.4. Use hidable instead.
      */
-    @Input() hiddable?: boolean; // Whether the user can hide the time left.
+    @Input({ transform: toBoolean }) hiddable = false; // Whether the user can hide the time left.
 
     timeLeft?: number; // Seconds left to end.
     modeBasic = CoreTimerMode.BASIC;
@@ -63,9 +64,9 @@ export class CoreTimerComponent implements OnInit, OnDestroy {
      */
     async ngOnInit(): Promise<void> {
         // eslint-disable-next-line deprecation/deprecation
-        if (this.hiddable !== undefined && this.hidable === undefined) {
-            // eslint-disable-next-line deprecation/deprecation
-            this.hidable = this.hiddable;
+        if (this.hiddable && !this.hidable) {
+
+            this.hidable = true;
         }
 
         const timeLeftClass = this.timeLeftClass || 'core-timer-timeleft-';
diff --git a/src/core/components/user-avatar/user-avatar.ts b/src/core/components/user-avatar/user-avatar.ts
index cc85a0f4c..de961ba4b 100644
--- a/src/core/components/user-avatar/user-avatar.ts
+++ b/src/core/components/user-avatar/user-avatar.ts
@@ -23,6 +23,7 @@ import { CoreNetwork } from '@services/network';
 import { CoreUserHelper } from '@features/user/services/user-helper';
 import { CoreUrl } from '@singletons/url';
 import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display a "user avatar".
@@ -40,11 +41,11 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
     @Input() site?: CoreSiteBasicInfo | CoreSiteInfo; // Site info contains user info.
     // The following params will override the ones in user object.
     @Input() profileUrl?: string;
-    @Input() linkProfile = true; // Avoid linking to the profile if wanted.
+    @Input({ transform: toBoolean }) linkProfile = true; // Avoid linking to the profile if wanted.
     @Input() fullname?: string;
     @Input() userId?: number; // If provided or found it will be used to link the image to the profile.
     @Input() courseId?: number;
-    @Input() checkOnline = false; // If want to check and show online status.
+    @Input({ transform: toBoolean }) checkOnline = false; // If want to check and show online status.
     @Input() siteId?: string;
 
     avatarUrl?: string;
diff --git a/src/core/directives/aria-button.ts b/src/core/directives/aria-button.ts
index ec05e4cab..c0edf0bee 100644
--- a/src/core/directives/aria-button.ts
+++ b/src/core/directives/aria-button.ts
@@ -14,6 +14,7 @@
 
 import { Directive, ElementRef, OnInit, Output, EventEmitter, OnChanges, SimpleChanges, Input } from '@angular/core';
 import { CoreDom } from '@singletons/dom';
+import { toBoolean } from '../transforms/boolean';
 
 /**
  * Directive to emulate click and key actions following aria role button.
@@ -25,7 +26,7 @@ export class CoreAriaButtonClickDirective implements OnInit, OnChanges {
 
     protected element: HTMLElement;
 
-    @Input() disabled = false;
+    @Input({ transform: toBoolean }) disabled = false;
     @Output() ariaButtonClick = new EventEmitter();
 
     constructor(
diff --git a/src/core/directives/auto-focus.ts b/src/core/directives/auto-focus.ts
index f531d74c0..7c35e9cba 100644
--- a/src/core/directives/auto-focus.ts
+++ b/src/core/directives/auto-focus.ts
@@ -15,9 +15,9 @@
 import { Directive, Input, ElementRef, AfterViewInit } from '@angular/core';
 
 import { CoreDomUtils } from '@services/utils/dom';
-import { CoreUtils } from '@services/utils/utils';
 import { CoreDom } from '@singletons/dom';
 import { CoreWait } from '@singletons/wait';
+import { toBoolean } from '../transforms/boolean';
 
 /**
  * Directive to auto focus an element when a view is loaded.
@@ -32,7 +32,7 @@ import { CoreWait } from '@singletons/wait';
 })
 export class CoreAutoFocusDirective implements AfterViewInit {
 
-    @Input('core-auto-focus') autoFocus: boolean | string = true;
+    @Input({ alias: 'core-auto-focus', transform: toBoolean }) autoFocus = true;
 
     protected element: HTMLIonInputElement | HTMLIonTextareaElement | HTMLIonSearchbarElement | HTMLElement;
 
@@ -44,7 +44,7 @@ export class CoreAutoFocusDirective implements AfterViewInit {
      * @inheritdoc
      */
     async ngAfterViewInit(): Promise<void> {
-        if (CoreUtils.isFalseOrZero(this.autoFocus)) {
+        if (!this.autoFocus) {
             return;
         }
 
diff --git a/src/core/directives/collapsible-footer.ts b/src/core/directives/collapsible-footer.ts
index 6fd746d42..7c87d1450 100644
--- a/src/core/directives/collapsible-footer.ts
+++ b/src/core/directives/collapsible-footer.ts
@@ -24,6 +24,7 @@ import { CoreLoadingComponent } from '@components/loading/loading';
 import { CoreCancellablePromise } from '@classes/cancellable-promise';
 import { CoreDom } from '@singletons/dom';
 import { CoreWait } from '@singletons/wait';
+import { toBoolean } from '../transforms/boolean';
 
 /**
  * Directive to make an element fixed at the bottom collapsible when scrolling.
@@ -37,7 +38,7 @@ import { CoreWait } from '@singletons/wait';
 })
 export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
 
-    @Input() appearOnBottom = false;
+    @Input({ transform: toBoolean }) appearOnBottom = false; // Whether footer should re-appear when reaching the bottom.
 
     protected id = '0';
     protected element: HTMLElement;
@@ -67,9 +68,6 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
      */
     async ngOnInit(): Promise<void> {
         this.id = String(CoreUtils.getUniqueId('CoreCollapsibleFooterDirective'));
-
-        // Only if not present or explicitly falsy it will be false.
-        this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom);
         this.slotPromise = CoreDom.slotOnContent(this.element);
 
         await this.slotPromise;
diff --git a/src/core/directives/collapsible-header.ts b/src/core/directives/collapsible-header.ts
index b20031439..1f09bad8f 100644
--- a/src/core/directives/collapsible-header.ts
+++ b/src/core/directives/collapsible-header.ts
@@ -20,7 +20,6 @@ import { CoreTabsOutletComponent } from '@components/tabs-outlet/tabs-outlet';
 import { CoreTabsComponent } from '@components/tabs/tabs';
 import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
 import { ScrollDetail } from '@ionic/core';
-import { CoreUtils } from '@services/utils/utils';
 import { CoreDirectivesRegistry } from '@singletons/directives-registry';
 import { CoreDom } from '@singletons/dom';
 import { CoreEventObserver, CoreEvents } from '@singletons/events';
@@ -28,6 +27,7 @@ import { CoreMath } from '@singletons/math';
 import { Subscription } from 'rxjs';
 import { CoreFormatTextDirective } from './format-text';
 import { CoreWait } from '@singletons/wait';
+import { toBoolean } from '../transforms/boolean';
 
 declare module '@singletons/events' {
 
@@ -75,7 +75,7 @@ export const COLLAPSIBLE_HEADER_UPDATED = 'collapsible_header_updated';
 })
 export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDestroy {
 
-    @Input() collapsible = true;
+    @Input({ transform: toBoolean }) collapsible = true;
 
     protected page?: HTMLElement;
     protected collapsedHeader: HTMLIonHeaderElement;
@@ -106,8 +106,6 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
      * @inheritdoc
      */
     ngOnInit(): void {
-        this.collapsible = !CoreUtils.isFalseOrZero(this.collapsible);
-
         if (CoreDom.closest(this.collapsedHeader, 'core-tabs-outlet')) {
             this.collapsible = false;
         }
@@ -142,7 +140,6 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
      */
     async ngOnChanges(changes: {[name: string]: SimpleChange}): Promise<void> {
         if (changes.collapsible && !changes.collapsible.firstChange) {
-            this.collapsible = !CoreUtils.isFalseOrZero(changes.collapsible.currentValue);
             this.enabled = this.collapsible;
 
             await this.init();
diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts
index eea0da0df..2af1d794c 100644
--- a/src/core/directives/format-text.ts
+++ b/src/core/directives/format-text.ts
@@ -56,6 +56,7 @@ import { CoreUrl } from '@singletons/url';
 import { CoreIcons } from '@singletons/icons';
 import { ContextLevel } from '../constants';
 import { CoreWait } from '@singletons/wait';
+import { toBoolean } from '../transforms/boolean';
 
 /**
  * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
@@ -77,19 +78,20 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
     @Input() siteId?: string; // Site ID to use.
     @Input() component?: string; // Component for CoreExternalContentDirective.
     @Input() componentId?: string | number; // Component ID to use in conjunction with the component.
-    @Input() adaptImg?: boolean | string = true; // Whether to adapt images to screen width.
-    @Input() clean?: boolean | string; // Whether all the HTML tags should be removed.
-    @Input() singleLine?: boolean | string; // Whether new lines should be removed (all text in single line). Only if clean=true.
+    @Input({ transform: toBoolean }) adaptImg = true; // Whether to adapt images to screen width.
+    @Input({ transform: toBoolean }) clean = false; // Whether all the HTML tags should be removed.
+    @Input({ transform: toBoolean }) singleLine = false; // Whether new lines should be removed. Only if clean=true.
     @Input() highlight?: string; // Text to highlight.
-    @Input() filter?: boolean | string; // Whether to filter the text. If not defined, true if contextLevel and instanceId are set.
+    @Input({ transform: toBoolean }) filter?: boolean; // Whether to filter the text.
+                                                       // If not defined, true if contextLevel and instanceId are set.
     @Input() contextLevel?: ContextLevel; // The context level of the text.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters.
-    @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the text for some reason.
-    @Input() captureLinks?: boolean; // Whether links should tried to be opened inside the app. Defaults to true.
-    @Input() openLinksInApp?: boolean; // Whether links should be opened in InAppBrowser.
-    @Input() hideIfEmpty = false; // If true, the tag will contain nothing if text is empty.
-    @Input() disabled?: boolean; // If disabled, autoplay elements will be disabled.
+    @Input({ transform: toBoolean }) wsNotFiltered = false; // If true it means the WS didn't filter the text for some reason.
+    @Input({ transform: toBoolean }) captureLinks = true; // Whether links should tried to be opened inside the app.
+    @Input({ transform: toBoolean }) openLinksInApp = false; // Whether links should be opened in InAppBrowser.
+    @Input({ transform: toBoolean }) hideIfEmpty = false; // If true, the tag will contain nothing if text is empty.
+    @Input({ transform: toBoolean }) disabled = false; // If disabled, autoplay elements will be disabled.
 
     @Output() afterRender: EventEmitter<void>; // Called when the data is rendered.
     @Output() onClick: EventEmitter<void> = new EventEmitter(); // Called when clicked.
@@ -361,7 +363,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
         }
 
         if (!this.element.getAttribute('singleLine')) {
-            this.element.setAttribute('singleLine', String(CoreUtils.isTrueOrOne(this.singleLine)));
+            this.element.setAttribute('singleLine', String(this.singleLine));
         }
 
         this.text = this.text ? this.text.trim() : '';
@@ -423,15 +425,14 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
             this.contextInstanceId = this.courseId;
         }
 
-        const filter = this.filter === undefined ?
-            !!(this.contextLevel && this.contextInstanceId !== undefined) : CoreUtils.isTrueOrOne(this.filter);
+        const filter = this.filter ?? !!(this.contextLevel && this.contextInstanceId !== undefined);
 
         const options: CoreFilterFormatTextOptions = {
-            clean: CoreUtils.isTrueOrOne(this.clean),
-            singleLine: CoreUtils.isTrueOrOne(this.singleLine),
+            clean: this.clean,
+            singleLine: this.singleLine,
             highlight: this.highlight,
             courseId: this.courseId,
-            wsNotFiltered: CoreUtils.isTrueOrOne(this.wsNotFiltered),
+            wsNotFiltered: this.wsNotFiltered,
         };
 
         let formatted: string;
@@ -521,7 +522,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
                     externalImages.push(externalImage);
                 }
 
-                if (CoreUtils.isTrueOrOne(this.adaptImg) && !img.classList.contains('icon')) {
+                if (this.adaptImg && !img.classList.contains('icon')) {
                     this.adaptImage(img);
                 }
             });
diff --git a/src/core/directives/link.ts b/src/core/directives/link.ts
index e3d1f3971..61ad4c40d 100644
--- a/src/core/directives/link.ts
+++ b/src/core/directives/link.ts
@@ -27,6 +27,7 @@ import { CoreCustomURLSchemes } from '@services/urlschemes';
 import { DomSanitizer } from '@singletons';
 import { CoreFilepool } from '@services/filepool';
 import { CoreDom } from '@singletons/dom';
+import { toBoolean } from '../transforms/boolean';
 
 /**
  * Directive to open a link in external browser or in the app.
@@ -37,10 +38,10 @@ import { CoreDom } from '@singletons/dom';
 export class CoreLinkDirective implements OnInit {
 
     @Input() href?: string | SafeUrl; // Link URL.
-    @Input() capture?: boolean | string; // If the link needs to be captured by the app.
-    @Input() inApp?: boolean | string; // True to open in embedded browser, false to open in system browser.
-    @Input() autoLogin: boolean | string = true; // Whether to try to use auto-login. Values yes/no/check are deprecated.
-    @Input() showBrowserWarning = true; // Whether to show a warning before opening browser. Defaults to true.
+    @Input({ transform: toBoolean }) capture = false; // If the link needs to be captured by the app.
+    @Input({ transform: toBoolean }) inApp = false; // True to open in embedded browser, false to open in system browser.
+    @Input({ transform: toBoolean }) autoLogin = true; // Whether to try to use auto-login.
+    @Input({ transform: toBoolean }) showBrowserWarning = true; // Whether to show a warning before opening browser.
 
     protected element: HTMLElement | HTMLIonFabButtonElement | HTMLIonButtonElement | HTMLIonItemElement;
 
@@ -93,7 +94,7 @@ export class CoreLinkDirective implements OnInit {
 
         const openIn = this.element.getAttribute('data-open-in');
 
-        if (CoreUtils.isTrueOrOne(this.capture)) {
+        if (this.capture) {
             const treated = await CoreContentLinksHelper.handleLink(CoreTextUtils.decodeURI(href), undefined, true, true);
 
             if (!treated) {
@@ -177,8 +178,7 @@ export class CoreLinkDirective implements OnInit {
      */
     protected async openExternalLink(href: string, openIn?: string | null): Promise<void> {
         // Priority order is: core-link inApp attribute > forceOpenLinksIn setting > data-open-in HTML attribute.
-        const openInApp = this.inApp !== undefined ?
-            CoreUtils.isTrueOrOne(this.inApp) :
+        const openInApp = this.inApp ??
             (CoreConstants.CONFIG.forceOpenLinksIn !== 'browser' &&
                 (CoreConstants.CONFIG.forceOpenLinksIn === 'app' || openIn === 'app'));
 
@@ -219,11 +219,7 @@ export class CoreLinkDirective implements OnInit {
             }
         }
 
-        const autoLogin = typeof this.autoLogin === 'boolean' ?
-            this.autoLogin :
-            !CoreUtils.isFalseOrZero(this.autoLogin) && this.autoLogin !== 'no'; // Support deprecated values yes/no/check.
-
-        if (autoLogin) {
+        if (this.autoLogin) {
             if (openInApp) {
                 await currentSite.openInAppWithAutoLogin(href);
             } else {
diff --git a/src/core/directives/user-tour.ts b/src/core/directives/user-tour.ts
index 04d3636ea..cb40cb38b 100644
--- a/src/core/directives/user-tour.ts
+++ b/src/core/directives/user-tour.ts
@@ -25,7 +25,7 @@ import { CoreDom } from '@singletons/dom';
 })
 export class CoreUserTourDirective implements OnInit, OnDestroy {
 
-    @Input() userTour!: CoreUserTourDirectiveOptions;
+    @Input({ required: true }) userTour!: CoreUserTourDirectiveOptions;
 
     private tour?: CoreUserToursUserTour | null;
     private element: HTMLElement;
diff --git a/src/core/features/block/classes/base-block-component.ts b/src/core/features/block/classes/base-block-component.ts
index 05bec595c..cfb6402c0 100644
--- a/src/core/features/block/classes/base-block-component.ts
+++ b/src/core/features/block/classes/base-block-component.ts
@@ -32,10 +32,10 @@ import { CorePromisedValue } from '@classes/promised-value';
 })
 export abstract class CoreBlockBaseComponent implements OnInit, OnChanges, ICoreBlockComponent, AsyncDirective {
 
-    @Input() title!: string; // The block title.
-    @Input() block!: CoreCourseBlock; // The block to render.
-    @Input() contextLevel!: ContextLevel; // The context where the block will be used.
-    @Input() instanceId!: number; // The instance ID associated with the context level.
+    @Input({ required: true }) title!: string; // The block title.
+    @Input({ required: true }) block!: CoreCourseBlock; // The block to render.
+    @Input({ required: true }) contextLevel!: ContextLevel; // The context where the block will be used.
+    @Input({ required: true }) instanceId!: number; // The instance ID associated with the context level.
     @Input() link?: string; // Link to go when clicked.
     @Input() linkParams?: Params; // Link params to go when clicked.
     @Input() navOptions?: CoreNavigationOptions; // Navigation options.
diff --git a/src/core/features/block/components/block/block.ts b/src/core/features/block/components/block/block.ts
index a89f4ee09..684e5c7cd 100644
--- a/src/core/features/block/components/block/block.ts
+++ b/src/core/features/block/components/block/block.ts
@@ -32,10 +32,10 @@ export class CoreBlockComponent implements OnChanges, OnDestroy {
 
     @ViewChild(CoreDynamicComponent) dynamicComponent?: CoreDynamicComponent<ICoreBlockComponent>;
 
-    @Input() block!: CoreCourseBlock; // The block to render.
-    @Input() contextLevel!: ContextLevel; // The context where the block will be used.
-    @Input() instanceId!: number; // The instance ID associated with the context level.
-    @Input() extraData!: Record<string, unknown>; // Any extra data to be passed to the block.
+    @Input({ required: true }) block!: CoreCourseBlock; // The block to render.
+    @Input({ required: true }) contextLevel!: ContextLevel; // The context where the block will be used.
+    @Input({ required: true }) instanceId!: number; // The instance ID associated with the context level.
+    @Input() extraData?: Record<string, unknown>; // Any extra data to be passed to the block.
     @Input() labelledBy?: string;
 
     componentClass?: Type<ICoreBlockComponent>; // The class of the component to render.
diff --git a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts
index a9d9f51ec..cf159c641 100644
--- a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts
+++ b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts
@@ -31,8 +31,8 @@ import { ContextLevel } from '@/core/constants';
 })
 export class CoreBlockSideBlocksButtonComponent implements OnInit, OnDestroy {
 
-    @Input() contextLevel!: ContextLevel;
-    @Input() instanceId!: number;
+    @Input({ required: true }) contextLevel!: ContextLevel;
+    @Input({ required: true }) instanceId!: number;
     @Input() myDashboardPage?: string;
 
     userTour: CoreUserTourDirectiveOptions = {
diff --git a/src/core/features/block/components/side-blocks/side-blocks.ts b/src/core/features/block/components/side-blocks/side-blocks.ts
index d4ac02876..23eee8649 100644
--- a/src/core/features/block/components/side-blocks/side-blocks.ts
+++ b/src/core/features/block/components/side-blocks/side-blocks.ts
@@ -42,8 +42,8 @@ import { CoreBlockComponentsModule } from '../components.module';
 })
 export class CoreBlockSideBlocksComponent implements OnInit {
 
-    @Input() contextLevel!: ContextLevel;
-    @Input() instanceId!: number;
+    @Input({ required: true }) contextLevel!: ContextLevel;
+    @Input({ required: true }) instanceId!: number;
     @Input() initialBlockInstanceId?: number;
     @Input() myDashboardPage?: string;
 
diff --git a/src/core/features/comments/components/comments/comments.ts b/src/core/features/comments/components/comments/comments.ts
index d780b9ebb..9e9f44828 100644
--- a/src/core/features/comments/components/comments/comments.ts
+++ b/src/core/features/comments/components/comments/comments.ts
@@ -22,6 +22,7 @@ import { CoreSites } from '@services/sites';
 import { CoreNavigator } from '@services/navigator';
 import { CoreUtils } from '@services/utils/utils';
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays the count of comments.
@@ -33,15 +34,15 @@ import { ContextLevel } from '@/core/constants';
 })
 export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestroy {
 
-    @Input() contextLevel!: ContextLevel;
-    @Input() instanceId!: number;
-    @Input() component!: string;
-    @Input() itemId!: number;
+    @Input({ required: true }) contextLevel!: ContextLevel;
+    @Input({ required: true }) instanceId!: number;
+    @Input({ required: true }) component!: string;
+    @Input({ required: true }) itemId!: number;
     @Input() area = '';
     @Input() title?: string;
     @Output() onLoading = new EventEmitter<boolean>();  // Event that indicates whether the component is loading data.
     @Input() courseId?: number; // Course ID the comments belong to. It can be used to improve performance with filters.
-    @Input() showItem = false; // Show button as an item.
+    @Input({ transform: toBoolean }) showItem = false; // Show button as an item.
 
     commentsLoaded = false;
     commentsCount = '';
diff --git a/src/core/features/compile/components/compile-html/compile-html.ts b/src/core/features/compile/components/compile-html/compile-html.ts
index 10c0c3d2a..dd6b6edbc 100644
--- a/src/core/features/compile/components/compile-html/compile-html.ts
+++ b/src/core/features/compile/components/compile-html/compile-html.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import {
     Component,
     Input,
@@ -32,6 +33,7 @@ import {
     AfterViewInit,
     Type,
     KeyValueDiffer,
+    Injector,
 } from '@angular/core';
 import { CorePromisedValue } from '@classes/promised-value';
 
@@ -63,14 +65,14 @@ import { CoreDom } from '@singletons/dom';
 })
 export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
 
-    @Input() text!: string; // The HTML text to display.
+    @Input({ required: true }) text!: string; // The HTML text to display.
     @Input() javascript?: string; // The Javascript to execute in the component.
     @Input() jsData?: Record<string, unknown>; // Data to pass to the fake component.
     @Input() cssCode?: string; // The styles to apply.
     @Input() stylesPath?: string; // The styles URL to apply (only if cssCode is not set).
     @Input() extraImports: unknown[] = []; // Extra import modules.
     @Input() extraProviders: Type<unknown>[] = []; // Extra providers.
-    @Input() forceCompile = false; // Set it to true to force compile even if the text/javascript hasn't changed.
+    @Input({ transform: toBoolean }) forceCompile = false; // True to force compile even if the text/javascript hasn't changed.
     @Output() created = new EventEmitter<unknown>(); // Will emit an event when the component is instantiated.
     @Output() compiling = new EventEmitter<boolean>(); // Event that indicates whether the template is being compiled.
 
@@ -88,6 +90,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
 
     constructor(
         protected changeDetector: ChangeDetectorRef,
+        protected injector: Injector,
         element: ElementRef,
         differs: KeyValueDiffers,
     ) {
@@ -122,7 +125,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
         // Only compile if text/javascript has changed or the forceCompile flag has been set to true.
         if (this.text === undefined ||
             !(changes.text || changes.javascript || changes.cssCode || changes.stylesPath ||
-                (changes.forceCompile && CoreUtils.isTrueOrOne(this.forceCompile)))) {
+                (changes.forceCompile && this.forceCompile))) {
             return;
         }
 
@@ -222,6 +225,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
                 CoreCompile.injectLibraries(
                     this,
                     compileInstance.extraProviders,
+                    compileInstance.injector,
                 );
 
                 // Always add these elements, they could be needed on component init (componentObservable).
diff --git a/src/core/features/compile/services/compile.ts b/src/core/features/compile/services/compile.ts
index 5bf93d338..476c60c0d 100644
--- a/src/core/features/compile/services/compile.ts
+++ b/src/core/features/compile/services/compile.ts
@@ -23,6 +23,10 @@ import {
     ViewContainerRef,
     signal,
     computed,
+    effect,
+    EffectCleanupRegisterFn,
+    CreateEffectOptions,
+    EffectRef,
 } from '@angular/core';
 import {
     ActionSheetController,
@@ -260,9 +264,10 @@ export class CoreCompileProvider {
      *
      * @param instance The instance where to inject the libraries.
      * @param extraLibraries Extra imported providers if needed and not imported by this class.
+     * @param injector Injector of the injection context. E.g. for a component, use the component's injector.
      */
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    injectLibraries(instance: any, extraLibraries: Type<unknown>[] = []): void {
+    injectLibraries(instance: any, extraLibraries: Type<unknown>[] = [], injector?: Injector): void {
         if (!this.libraries || !this.exportedObjects) {
             throw new CoreError('Libraries not loaded. You need to call loadLibraries before calling injectLibraries.');
         }
@@ -271,6 +276,7 @@ export class CoreCompileProvider {
             ...this.libraries,
             ...extraLibraries,
         ];
+        injector = injector ?? this.injector;
 
         // We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
         for (const i in libraries) {
@@ -278,7 +284,7 @@ export class CoreCompileProvider {
             if (typeof libraryDef === 'function' && libraryDef.name) {
                 try {
                     // Inject the provider to the instance. We use the class name as the property name.
-                    instance[libraryDef.name.replace(/DelegateService$/, 'Delegate')] = this.injector.get<Provider>(libraryDef);
+                    instance[libraryDef.name.replace(/DelegateService$/, 'Delegate')] = injector.get<Provider>(libraryDef);
                 } catch (ex) {
                     this.logger.error('Error injecting provider', libraryDef.name, ex);
                 }
@@ -289,17 +295,26 @@ export class CoreCompileProvider {
         instance['CoreCompileProvider'] = this;
 
         // Add some final classes.
-        instance['injector'] = this.injector;
+        instance['injector'] = injector;
         instance['Validators'] = Validators;
         instance['CoreConstants'] = CoreConstants;
         instance['DownloadStatus'] = DownloadStatus;
         instance['CoreConfigConstants'] = CoreConstants.CONFIG;
         instance['CoreEventsProvider'] = CoreEvents;
         instance['CoreLoggerProvider'] = CoreLogger;
-        instance['signal'] = signal;
-        instance['computed'] = computed;
         instance['moment'] = moment;
         instance['Md5'] = Md5;
+        instance['signal'] = signal;
+        instance['computed'] = computed;
+        // Create a wrapper to call effect with the proper injection context.
+        instance['effect'] = (
+            effectFn: (onCleanup: EffectCleanupRegisterFn) => void,
+            options?: Omit<CreateEffectOptions, 'injector'>,
+        ): EffectRef =>
+            effect(effectFn, {
+                ...options,
+                injector,
+            });
         /**
          * @deprecated since 4.1, plugins should use CoreNetwork instead.
          * Keeping this a bit more to avoid plugins breaking.
diff --git a/src/core/features/contentlinks/components/choose-site-modal/choose-site-modal.ts b/src/core/features/contentlinks/components/choose-site-modal/choose-site-modal.ts
index 15c4e29d1..fa546b317 100644
--- a/src/core/features/contentlinks/components/choose-site-modal/choose-site-modal.ts
+++ b/src/core/features/contentlinks/components/choose-site-modal/choose-site-modal.ts
@@ -36,7 +36,7 @@ import { CoreSharedModule } from '@/core/shared.module';
 })
 export class CoreContentLinksChooseSiteModalComponent implements OnInit {
 
-    @Input() url!: string;
+    @Input({ required: true }) url!: string;
 
     sites: CoreSiteBasicInfo[] = [];
     loaded = false;
diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts
index e3b994b08..40a2e7cda 100644
--- a/src/core/features/course/classes/main-resource-component.ts
+++ b/src/core/features/course/classes/main-resource-component.ts
@@ -52,8 +52,8 @@ export type CoreCourseResourceDownloadResult = {
 })
 export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, CoreCourseModuleMainComponent {
 
-    @Input() module!: CoreCourseModuleData; // The module of the component.
-    @Input() courseId!: number; // Course ID the component belongs to.
+    @Input({ required: true }) module!: CoreCourseModuleData; // The module of the component.
+    @Input({ required: true }) courseId!: number; // Course ID the component belongs to.
     @Output() dataRetrieved = new EventEmitter<unknown>(); // Called to notify changes the index page from the main component.
 
     showLoading = true; // Whether to show loading.
diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts
index 311864b22..6ff2a5804 100644
--- a/src/core/features/course/components/course-format/course-format.ts
+++ b/src/core/features/course/components/course-format/course-format.ts
@@ -58,6 +58,7 @@ import { CoreBlockComponentsModule } from '@features/block/components/components
 import { CoreCourseComponentsModule } from '../components.module';
 import { CoreSites } from '@services/sites';
 import { COURSE_ALL_SECTIONS_PREFERRED_PREFIX } from '@features/course/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display course contents using a certain format. If the format isn't found, use default one.
@@ -84,13 +85,13 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
 
     static readonly LOAD_MORE_ACTIVITIES = 10; // How many activities should load each time showMoreActivities is called.
 
-    @Input() course!: CoreCourseAnyCourseData; // The course to render.
+    @Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render.
     @Input() sections: CoreCourseSectionToDisplay[] = []; // List of course sections.
     @Input() initialSectionId?: number; // The section to load first (by ID).
     @Input() initialSectionNumber?: number; // The section to load first (by number).
     @Input() initialBlockInstanceId?: number; // The instance to focus.
     @Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
-    @Input() isGuest?: boolean; // If user is accessing using an ACCESS_GUEST enrolment method.
+    @Input({ transform: toBoolean }) isGuest = false; // If user is accessing using an ACCESS_GUEST enrolment method.
 
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
     @ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList<CoreDynamicComponent<any>>;
diff --git a/src/core/features/course/components/module-completion/module-completion.ts b/src/core/features/course/components/module-completion/module-completion.ts
index dccb6d573..9962b2631 100644
--- a/src/core/features/course/components/module-completion/module-completion.ts
+++ b/src/core/features/course/components/module-completion/module-completion.ts
@@ -24,6 +24,7 @@ import { CoreCourseHelper } from '@features/course/services/course-helper';
 import { CoreUser } from '@features/user/services/user';
 import { Translate } from '@singletons';
 import { CoreEventObserver, CoreEvents } from '@singletons/events';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing
@@ -43,8 +44,8 @@ export class CoreCourseModuleCompletionComponent
     extends CoreCourseModuleCompletionBaseComponent
     implements OnInit, OnChanges, OnDestroy {
 
-    @Input() showCompletionConditions = false; // Whether to show activity completion conditions.
-    @Input() showManualCompletion = false; // Whether to show manual completion.
+    @Input({ transform: toBoolean }) showCompletionConditions = false; // Whether to show activity completion conditions.
+    @Input({ transform: toBoolean }) showManualCompletion = false; // Whether to show manual completion.
 
     completed = false;
     accessibleDescription: string | null = null;
diff --git a/src/core/features/course/components/module-description/module-description.ts b/src/core/features/course/components/module-description/module-description.ts
index f55b42cb8..8bf369c24 100644
--- a/src/core/features/course/components/module-description/module-description.ts
+++ b/src/core/features/course/components/module-description/module-description.ts
@@ -14,6 +14,7 @@
 
 import { ContextLevel } from '@/core/constants';
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, HostBinding, Input } from '@angular/core';
 
 /**
@@ -49,7 +50,7 @@ export class CoreCourseModuleDescriptionComponent {
     @Input() note?: string; // A note to display along with the description.
     @Input() component?: string; // Component for format text directive.
     @Input() componentId?: string | number; // Component ID to use in conjunction with the component.
-    @Input() showFull?: string | boolean; // Whether to always display the full description.
+    @Input({ transform: toBoolean }) showFull = false; // Whether to always display the full description.
     @Input() contextLevel?: ContextLevel; // The context level.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters.
diff --git a/src/core/features/course/components/module-info/module-info.ts b/src/core/features/course/components/module-info/module-info.ts
index 75999e0d8..fee40aba2 100644
--- a/src/core/features/course/components/module-info/module-info.ts
+++ b/src/core/features/course/components/module-info/module-info.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
 import { CoreCourse } from '@features/course/services/course';
 import { CoreCourseModuleCompletionData, CoreCourseModuleData } from '@features/course/services/course-helper';
@@ -35,20 +36,20 @@ import { CoreSites } from '@services/sites';
 })
 export class CoreCourseModuleInfoComponent implements OnInit {
 
-    @Input() module!: CoreCourseModuleData; // The module to render.
-    @Input() courseId!: number; // The courseId the module belongs to.
+    @Input({ required: true }) module!: CoreCourseModuleData; // The module to render.
+    @Input({ required: true }) courseId!: number; // The courseId the module belongs to.
 
-    @Input() component!: string; // Component for format text directive.
-    @Input() componentId!: string | number; // Component ID to use in conjunction with the component.
+    @Input({ required: true }) component!: string; // Component for format text directive.
+    @Input({ required: true }) componentId!: string | number; // Component ID to use in conjunction with the component.
 
     @Input() description?: string | false; // The description to display. If false, no description will be shown.
-    @Input() expandDescription = false; // If the description should be expanded by default.
+    @Input({ transform: toBoolean }) expandDescription = false; // If the description should be expanded by default.
 
-    @Input() showAvailabilityInfo = false; // If show availability info on the box.
+    @Input({ transform: toBoolean }) showAvailabilityInfo = false; // If show availability info on the box.
 
-    @Input() hasDataToSync = false; // If the activity has any data to be synced.
+    @Input({ transform: toBoolean }) hasDataToSync = false; // If the activity has any data to be synced.
 
-    @Input() showManualCompletion = true; // Whether to show manual completion, true by default.
+    @Input({ transform: toBoolean }) showManualCompletion = true; // Whether to show manual completion, true by default.
     @Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes.
 
     modicon = '';
diff --git a/src/core/features/course/components/module-navigation/module-navigation.ts b/src/core/features/course/components/module-navigation/module-navigation.ts
index 33aca98d5..ce555d494 100644
--- a/src/core/features/course/components/module-navigation/module-navigation.ts
+++ b/src/core/features/course/components/module-navigation/module-navigation.ts
@@ -36,8 +36,8 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
 })
 export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
 
-    @Input() courseId!: number; // Course ID.
-    @Input() currentModuleId!: number; // Current module Id.
+    @Input({ required: true }) courseId!: number; // Course ID.
+    @Input({ required: true }) currentModuleId!: number; // Current module Id.
 
     nextModule?: CoreCourseModuleData;
     previousModule?: CoreCourseModuleData;
diff --git a/src/core/features/course/components/module-summary/module-summary.ts b/src/core/features/course/components/module-summary/module-summary.ts
index 45c6fe5d9..a14798a75 100644
--- a/src/core/features/course/components/module-summary/module-summary.ts
+++ b/src/core/features/course/components/module-summary/module-summary.ts
@@ -34,6 +34,7 @@ import { ModalController, NgZone } from '@singletons';
 import { CoreEventObserver, CoreEvents } from '@singletons/events';
 import { Subscription } from 'rxjs';
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display a module summary modal.
@@ -54,7 +55,7 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
     @Input() moduleId = 0; // Module ID the component belongs to.
     @Input() component = ''; // Component name.
     @Input() description = ''; // Module description.
-    @Input() hasOffline = false; // If it has offline data to be synced.
+    @Input({ transform: toBoolean }) hasOffline = false; // If it has offline data to be synced.
     @Input() displayOptions: CoreCourseModuleSummaryDisplayOptions = {};
 
     loaded = false; // If the component has been loaded.
diff --git a/src/core/features/course/components/module/module.ts b/src/core/features/course/components/module/module.ts
index f8c867751..afe4c1545 100644
--- a/src/core/features/course/components/module/module.ts
+++ b/src/core/features/course/components/module/module.ts
@@ -30,6 +30,7 @@ import {
 import { CoreConstants, DownloadStatus } from '@/core/constants';
 import { CoreEventObserver, CoreEvents } from '@singletons/events';
 import { BehaviorSubject } from 'rxjs';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display a module entry in a list of modules.
@@ -45,17 +46,17 @@ import { BehaviorSubject } from 'rxjs';
 })
 export class CoreCourseModuleComponent implements OnInit, OnDestroy {
 
-    @Input() module!: CoreCourseModuleData; // The module to render.
+    @Input({ required: true }) module!: CoreCourseModuleData; // The module to render.
     @Input() section?: CoreCourseSection; // The section the module belongs to.
-    @Input() showActivityDates = false; // Whether to show activity dates.
-    @Input() showCompletionConditions = false; // Whether to show activity completion conditions.
-    @Input() showLegacyCompletion?: boolean; // Whether to show module completion in the old format.
-    @Input() showCompletion = true; // Whether to show module completion.
-    @Input() showAvailability = true; // Whether to show module availability.
-    @Input() showExtra = true; // Whether to show extra badges.
-    @Input() showDownloadStatus = true; // Whether to show download status.
-    @Input() showIndentation = true; // Whether to show indentation
-    @Input() isLastViewed = false; // Whether it's the last module viewed in a course.
+    @Input({ transform: toBoolean }) showActivityDates = false; // Whether to show activity dates.
+    @Input({ transform: toBoolean }) showCompletionConditions = false; // Whether to show activity completion conditions.
+    @Input({ transform: toBoolean }) showLegacyCompletion?: boolean; // Whether to show module completion in the old format.
+    @Input({ transform: toBoolean }) showCompletion = true; // Whether to show module completion.
+    @Input({ transform: toBoolean }) showAvailability = true; // Whether to show module availability.
+    @Input({ transform: toBoolean }) showExtra = true; // Whether to show extra badges.
+    @Input({ transform: toBoolean }) showDownloadStatus = true; // Whether to show download status.
+    @Input({ transform: toBoolean }) showIndentation = true; // Whether to show indentation
+    @Input({ transform: toBoolean }) isLastViewed = false; // Whether it's the last module viewed in a course.
     @Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when module completion changes.
     @HostBinding('class.indented') indented = false;
 
diff --git a/src/core/features/courses/components/course-list-item/course-list-item.ts b/src/core/features/courses/components/course-list-item/course-list-item.ts
index ebf2a8bdd..344cdf30b 100644
--- a/src/core/features/courses/components/course-list-item/course-list-item.ts
+++ b/src/core/features/courses/components/course-list-item/course-list-item.ts
@@ -28,6 +28,7 @@ import { CoreCoursesHelper, CoreEnrolledCourseDataWithExtraInfoAndOptions } from
 import { CoreCoursesCourseOptionsMenuComponent } from '../course-options-menu/course-options-menu';
 import { CoreEnrolHelper } from '@features/enrol/services/enrol-helper';
 import { CoreDownloadStatusTranslatable } from '@components/download-refresh/download-refresh';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * This directive is meant to display an item for a list of courses.
@@ -43,8 +44,8 @@ import { CoreDownloadStatusTranslatable } from '@components/download-refresh/dow
 })
 export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, OnChanges {
 
-    @Input() course!: CoreCourseListItem; // The course to render.
-    @Input() showDownload = false; // If true, will show download button.
+    @Input({ required: true }) course!: CoreCourseListItem; // The course to render.
+    @Input({ transform: toBoolean }) showDownload = false; // If true, will show download button.
     @Input() layout: 'listwithenrol'|'summarycard'|'list'|'card' = 'listwithenrol';
 
     enrolmentIcons: CoreCoursesEnrolmentIcons[] = [];
diff --git a/src/core/features/courses/components/course-options-menu/course-options-menu.ts b/src/core/features/courses/components/course-options-menu/course-options-menu.ts
index b8bece69c..271c3e07f 100644
--- a/src/core/features/courses/components/course-options-menu/course-options-menu.ts
+++ b/src/core/features/courses/components/course-options-menu/course-options-menu.ts
@@ -27,8 +27,8 @@ import { PopoverController } from '@singletons';
 })
 export class CoreCoursesCourseOptionsMenuComponent implements OnInit {
 
-    @Input() course!: CoreEnrolledCourseDataWithExtraInfoAndOptions; // The course.
-    @Input() prefetch!: CorePrefetchStatusInfo; // The prefecth info.
+    @Input({ required: true }) course!: CoreEnrolledCourseDataWithExtraInfoAndOptions; // The course.
+    @Input({ required: true }) prefetch!: CorePrefetchStatusInfo; // The prefecth info.
 
     downloadCourseEnabled = false;
 
diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts
index 9a27b95d1..5e4fb95ea 100644
--- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts
+++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts
@@ -47,6 +47,7 @@ import { ContextLevel } from '@/core/constants';
 import { CoreSwiper } from '@singletons/swiper';
 import { CoreTextUtils } from '@services/utils/text';
 import { CoreWait } from '@singletons/wait';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display a rich text editor if enabled.
@@ -73,7 +74,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
     @Input() name = 'core-rich-text-editor'; // Name to set to the textarea.
     @Input() component?: string; // The component to link the files to.
     @Input() componentId?: number; // An ID to use in conjunction with the component.
-    @Input() autoSave?: boolean | string; // Whether to auto-save the contents in a draft. Defaults to true.
+    @Input({ transform: toBoolean }) autoSave = true; // Whether to auto-save the contents in a draft.
     @Input() contextLevel?: ContextLevel; // The context level of the text.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() elementId?: string; // An ID to set to the element.
@@ -887,7 +888,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
      */
     protected shouldAutoSaveDrafts(): boolean {
         return !!CoreSites.getCurrentSite() &&
-                (this.autoSave === undefined || CoreUtils.isTrueOrOne(this.autoSave)) &&
+                this.autoSave &&
                 this.contextLevel !== undefined &&
                 this.contextInstanceId !== undefined &&
                 this.elementId !== undefined;
diff --git a/src/core/features/emulator/components/capture-media/capture-media.ts b/src/core/features/emulator/components/capture-media/capture-media.ts
index 9c7c51724..c997268e4 100644
--- a/src/core/features/emulator/components/capture-media/capture-media.ts
+++ b/src/core/features/emulator/components/capture-media/capture-media.ts
@@ -24,6 +24,7 @@ import { CoreError } from '@classes/errors/error';
 import { CoreCaptureError } from '@classes/errors/captureerror';
 import { CoreCanceledError } from '@classes/errors/cancelederror';
 import { CorePath } from '@singletons/path';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Page to capture media in browser.
@@ -41,7 +42,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
     @Input() mimetype?: string;
     @Input() extension?: string;
     @Input() quality?: number; // Only for images.
-    @Input() returnDataUrl?: boolean; // Whether it should return a data img. Only for images.
+    @Input({ transform: toBoolean }) returnDataUrl = false; // Whether it should return a data img. Only for images.
 
     @ViewChild('streamVideo') streamVideo?: ElementRef;
     @ViewChild('previewVideo') previewVideo?: ElementRef;
diff --git a/src/core/features/fileuploader/components/audio-histogram/audio-histogram.ts b/src/core/features/fileuploader/components/audio-histogram/audio-histogram.ts
index 7ced49f26..cdd0e8cd0 100644
--- a/src/core/features/fileuploader/components/audio-histogram/audio-histogram.ts
+++ b/src/core/features/fileuploader/components/audio-histogram/audio-histogram.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core';
 
 @Component({
@@ -31,8 +32,8 @@ export class CoreFileUploaderAudioHistogramComponent implements AfterViewInit, O
     private static readonly BARS_MIN_HEIGHT = 4;
     private static readonly BARS_GUTTER = 4;
 
-    @Input() analyser!: AnalyserNode;
-    @Input() paused?: boolean;
+    @Input({ required: true }) analyser!: AnalyserNode;
+    @Input({ transform: toBoolean }) paused = false;
     @ViewChild('canvas') canvasRef?: ElementRef<HTMLCanvasElement>;
 
     private element: HTMLElement;
diff --git a/src/core/features/h5p/components/h5p-iframe/h5p-iframe.ts b/src/core/features/h5p/components/h5p-iframe/h5p-iframe.ts
index 0fd4d67b3..d3542f469 100644
--- a/src/core/features/h5p/components/h5p-iframe/h5p-iframe.ts
+++ b/src/core/features/h5p/components/h5p-iframe/h5p-iframe.ts
@@ -30,6 +30,7 @@ import { CoreLogger } from '@singletons/logger';
 import { CoreH5PCore, CoreH5PDisplayOptions } from '../../classes/core';
 import { CoreH5PHelper } from '../../classes/helper';
 import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to render an iframe with an H5P package.
@@ -45,7 +46,7 @@ export class CoreH5PIframeComponent implements OnChanges, OnDestroy {
     @Input() onlinePlayerUrl?: string; // The URL of the online player to display the H5P package.
     @Input() trackComponent?: string; // Component to send xAPI events to.
     @Input() contextId?: number; // Context ID. Required for tracking.
-    @Input() enableInAppFullscreen?: boolean; // Whether to enable our custom in-app fullscreen feature.
+    @Input({ transform: toBoolean }) enableInAppFullscreen = false; // Whether to enable our custom in-app fullscreen feature.
     @Input() saveFreq?: number; // Save frequency (in seconds) if enabled.
     @Input() state?: string; // Initial content state.
     @Output() onIframeUrlSet = new EventEmitter<{src: string; online: boolean}>();
diff --git a/src/core/features/login/components/exceeded-attempts/exceeded-attempts.ts b/src/core/features/login/components/exceeded-attempts/exceeded-attempts.ts
index 867dc4e45..57b4b8a30 100644
--- a/src/core/features/login/components/exceeded-attempts/exceeded-attempts.ts
+++ b/src/core/features/login/components/exceeded-attempts/exceeded-attempts.ts
@@ -23,7 +23,7 @@ import { CoreUserSupport } from '@features/user/services/support';
 })
 export class CoreLoginExceededAttemptsComponent implements OnInit {
 
-    @Input() supportConfig!: CoreUserSupportConfig;
+    @Input({ required: true }) supportConfig!: CoreUserSupportConfig;
     @Input() supportSubject?: string;
 
     canContactSupport = false;
diff --git a/src/core/features/login/components/login-methods/login-methods.ts b/src/core/features/login/components/login-methods/login-methods.ts
index b7f252a34..88ac5a23e 100644
--- a/src/core/features/login/components/login-methods/login-methods.ts
+++ b/src/core/features/login/components/login-methods/login-methods.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, OnInit } from '@angular/core';
 import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/sites/unauthenticated-site';
 import { CoreLoginHelper, CoreLoginMethod } from '@features/login/services/login-helper';
@@ -27,7 +28,7 @@ import { CoreDomUtils } from '@services/utils/dom';
 })
 export class CoreLoginMethodsComponent implements OnInit {
 
-    @Input() reconnect = false;
+    @Input({ transform: toBoolean }) reconnect = false;
     @Input() siteUrl = '';
     @Input() siteConfig?: CoreSitePublicConfigResponse;
     @Input() redirectData?: CoreRedirectPayload;
diff --git a/src/core/features/login/pages/email-signup/email-signup.html b/src/core/features/login/pages/email-signup/email-signup.html
index 51c27f859..ff8eb087e 100644
--- a/src/core/features/login/pages/email-signup/email-signup.html
+++ b/src/core/features/login/pages/email-signup/email-signup.html
@@ -27,7 +27,7 @@
                         {{ 'core.login.signuprequiredfieldnotsupported' | translate }}
                     </ion-label>
                 </ion-item>
-                <ion-button expand="block" [href]="signupUrl" core-link autoLogin="no" [showBrowserWarning]="false">
+                <ion-button expand="block" [href]="signupUrl" core-link [autoLogin]="false" [showBrowserWarning]="false">
                     {{ 'core.openinbrowser' | translate }}
                 </ion-button>
             </ion-list>
diff --git a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
index 369dfc41e..a15b2a8e7 100644
--- a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
+++ b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
@@ -22,6 +22,7 @@ import { CoreSites } from '@services/sites';
 import { CoreModals } from '@services/modals';
 import { CoreMainMenuUserMenuTourComponent } from '../user-menu-tour/user-menu-tour';
 import { CoreMainMenuPage } from '@features/mainmenu/pages/menu/menu';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display an avatar on the header to open user menu.
@@ -35,7 +36,7 @@ import { CoreMainMenuPage } from '@features/mainmenu/pages/menu/menu';
 })
 export class CoreMainMenuUserButtonComponent implements OnInit {
 
-    @Input() alwaysShow = false;
+    @Input({ transform: toBoolean }) alwaysShow = false;
     siteInfo?: CoreSiteInfo;
     isMainScreen = false;
     userTour: CoreUserTourDirectiveOptions = {
diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.html b/src/core/features/mainmenu/components/user-menu/user-menu.html
index 6dc40ca15..7063eba8e 100644
--- a/src/core/features/mainmenu/components/user-menu/user-menu.html
+++ b/src/core/features/mainmenu/components/user-menu/user-menu.html
@@ -17,7 +17,7 @@
         <ion-list>
             <!-- Site info with URL and clickable. -->
             <ion-item button class="core-usermenu-siteinfo ion-text-wrap" *ngIf="siteInfo && displaySiteUrl" lines="full" [detail]="false"
-                [href]="siteUrl" core-link auto-login="yes">
+                [href]="siteUrl" core-link>
                 <ion-label>
                     <!-- Show site logo. -->
                     <img class="core-usermenu-site-logo" *ngIf="siteLogo && siteLogoLoaded" [src]="siteLogo" role="presentation" alt=""
@@ -25,7 +25,7 @@
                     <p class="core-usermenu-sitename">
                         <core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [wsNotFiltered]="true" />
                     </p>
-                    <a [href]="siteUrl" core-link auto-login="yes" class="core-usermenu-siteurl">{{ siteUrl }}</a>
+                    <a [href]="siteUrl" core-link class="core-usermenu-siteurl">{{ siteUrl }}</a>
                 </ion-label>
             </ion-item>
             <!-- Site info without URL and not clickable. -->
diff --git a/src/core/features/question/classes/base-question-component.ts b/src/core/features/question/classes/base-question-component.ts
index 09642ba35..78fc86068 100644
--- a/src/core/features/question/classes/base-question-component.ts
+++ b/src/core/features/question/classes/base-question-component.ts
@@ -24,6 +24,7 @@ import { CoreIonicColorNames } from '@singletons/colors';
 import { CoreLogger } from '@singletons/logger';
 import { CoreQuestionBehaviourButton, CoreQuestionHelper, CoreQuestionQuestion } from '../services/question-helper';
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Base class for components to render a question.
@@ -37,11 +38,11 @@ export class CoreQuestionBaseComponent<T extends AddonModQuizQuestion = AddonMod
     @Input() component?: string; // The component the question belongs to.
     @Input() componentId?: number; // ID of the component the question belongs to.
     @Input() attemptId?: number; // Attempt ID.
-    @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
+    @Input({ transform: toBoolean }) offlineEnabled = false; // Whether the question can be answered in offline.
     @Input() contextLevel?: ContextLevel; // The context level.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // The course the question belongs to (if any).
-    @Input() review?: boolean; // Whether the user is in review mode.
+    @Input({ transform: toBoolean }) review = false; // Whether the user is in review mode.
     @Input() preferredBehaviour?: string; // Preferred behaviour.
     @Output() buttonClicked = new EventEmitter<CoreQuestionBehaviourButton>(); // Will emit when a behaviour button is clicked.
     @Output() onAbort = new EventEmitter<void>(); // Should emit an event if the question should be aborted.
diff --git a/src/core/features/question/components/question/question.ts b/src/core/features/question/components/question/question.ts
index 7932bcc1b..7a7fbf7ef 100644
--- a/src/core/features/question/components/question/question.ts
+++ b/src/core/features/question/components/question/question.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, Output, OnInit, EventEmitter, ChangeDetectorRef, Type, ElementRef } from '@angular/core';
 import { AsyncDirective } from '@classes/async-directive';
 import { CorePromisedValue } from '@classes/promised-value';
@@ -41,11 +42,11 @@ export class CoreQuestionComponent implements OnInit, AsyncDirective {
     @Input() componentId?: number; // ID of the component the question belongs to.
     @Input() attemptId?: number; // Attempt ID.
     @Input() usageId?: number; // Usage ID.
-    @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
+    @Input({ transform: toBoolean }) offlineEnabled = false; // Whether the question can be answered in offline.
     @Input() contextLevel?: ContextLevel; // The context level.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters.
-    @Input() review?: boolean; // Whether the user is in review mode.
+    @Input({ transform: toBoolean }) review = false; // Whether the user is in review mode.
     @Input() preferredBehaviour?: string; // Behaviour to use.
     @Output() buttonClicked = new EventEmitter<CoreQuestionBehaviourButton>(); // Will emit when a behaviour button is clicked.
     @Output() onAbort= new EventEmitter<void>(); // Will emit an event if the question should be aborted.
@@ -78,8 +79,6 @@ export class CoreQuestionComponent implements OnInit, AsyncDirective {
      * @inheritdoc
      */
     async ngOnInit(): Promise<void> {
-        this.offlineEnabled = CoreUtils.isTrueOrOne(this.offlineEnabled);
-
         if (!this.question || (this.question.type != 'random' &&
                 !CoreQuestionDelegate.isQuestionSupported(this.question.type))) {
             this.promisedReady.resolve();
diff --git a/src/core/features/rating/components/aggregate/aggregate.ts b/src/core/features/rating/components/aggregate/aggregate.ts
index 7837e6343..9687202ed 100644
--- a/src/core/features/rating/components/aggregate/aggregate.ts
+++ b/src/core/features/rating/components/aggregate/aggregate.ts
@@ -33,12 +33,12 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
 })
 export class CoreRatingAggregateComponent implements OnChanges, OnDestroy {
 
-    @Input() ratingInfo!: CoreRatingInfo;
-    @Input() contextLevel!: ContextLevel;
-    @Input() instanceId!: number;
-    @Input() itemId!: number;
-    @Input() aggregateMethod!: number;
-    @Input() scaleId!: number;
+    @Input({ required: true }) ratingInfo!: CoreRatingInfo;
+    @Input({ required: true }) contextLevel!: ContextLevel;
+    @Input({ required: true }) instanceId!: number;
+    @Input({ required: true }) itemId!: number;
+    @Input({ required: true }) aggregateMethod!: number;
+    @Input({ required: true }) scaleId!: number;
     @Input() courseId?: number;
 
     item?: CoreRatingInfoItem;
diff --git a/src/core/features/rating/components/rate/rate.ts b/src/core/features/rating/components/rate/rate.ts
index a7b9c562e..5768ce812 100644
--- a/src/core/features/rating/components/rate/rate.ts
+++ b/src/core/features/rating/components/rate/rate.ts
@@ -37,15 +37,15 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
 })
 export class CoreRatingRateComponent implements OnChanges, OnDestroy {
 
-    @Input() ratingInfo!: CoreRatingInfo;
-    @Input() contextLevel!: ContextLevel; // Context level: course, module, user, etc.
-    @Input() instanceId!: number; // Context instance id.
-    @Input() itemId!: number; // Item id. Example: forum post id.
-    @Input() itemSetId!: number; // Item set id. Example: forum discussion id.
-    @Input() courseId!: number;
-    @Input() aggregateMethod!: number;
-    @Input() scaleId!: number;
-    @Input() userId!: number;
+    @Input({ required: true }) ratingInfo!: CoreRatingInfo;
+    @Input({ required: true }) contextLevel!: ContextLevel; // Context level: course, module, user, etc.
+    @Input({ required: true }) instanceId!: number; // Context instance id.
+    @Input({ required: true }) itemId!: number; // Item id. Example: forum post id.
+    @Input({ required: true }) itemSetId!: number; // Item set id. Example: forum discussion id.
+    @Input({ required: true }) courseId!: number;
+    @Input({ required: true }) aggregateMethod!: number;
+    @Input({ required: true }) scaleId!: number;
+    @Input({ required: true }) userId!: number;
     @Output() protected onLoading: EventEmitter<boolean>; // Eevent that indicates whether the component is loading data.
     @Output() protected onUpdate: EventEmitter<void>; // Event emitted when the rating is updated online.
 
diff --git a/src/core/features/rating/components/ratings/ratings.ts b/src/core/features/rating/components/ratings/ratings.ts
index 5dc9aa6f8..005476c11 100644
--- a/src/core/features/rating/components/ratings/ratings.ts
+++ b/src/core/features/rating/components/ratings/ratings.ts
@@ -31,14 +31,14 @@ import { ModalController } from '@singletons';
 })
 export class CoreRatingRatingsComponent implements OnInit {
 
-    @Input() contextLevel!: ContextLevel;
-    @Input() instanceId!: number;
-    @Input() ratingComponent!: string;
-    @Input() ratingArea!: string;
-    @Input() aggregateMethod!: number;
-    @Input() itemId!: number;
-    @Input() scaleId!: number;
-    @Input() courseId!: number;
+    @Input({ required: true }) contextLevel!: ContextLevel;
+    @Input({ required: true }) instanceId!: number;
+    @Input({ required: true }) ratingComponent!: string;
+    @Input({ required: true }) ratingArea!: string;
+    @Input({ required: true }) aggregateMethod!: number;
+    @Input({ required: true }) itemId!: number;
+    @Input({ required: true }) scaleId!: number;
+    @Input({ required: true }) courseId!: number;
 
     loaded = false;
     ratings: CoreRatingItemRating[] = [];
diff --git a/src/core/features/reportbuilder/components/report-column/report-column.ts b/src/core/features/reportbuilder/components/report-column/report-column.ts
index 5d086c93a..427000145 100644
--- a/src/core/features/reportbuilder/components/report-column/report-column.ts
+++ b/src/core/features/reportbuilder/components/report-column/report-column.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, EventEmitter, Input, Output } from '@angular/core';
 import { CoreReportBuilder } from '@features/reportbuilder/services/reportbuilder';
 
@@ -22,15 +23,15 @@ import { CoreReportBuilder } from '@features/reportbuilder/services/reportbuilde
 })
 export class CoreReportBuilderReportColumnComponent {
 
-    @Input() isExpanded = false;
-    @Input() isExpandable = false;
-    @Input() showFirstTitle = false;
-    @Input() columnIndex!: number;
-    @Input() rowIndex!: number;
-    @Input() column!: string | number;
-    @Input() contextId!: number;
-    @Input() header!: string;
-    @Input() source!: string;
+    @Input({ transform: toBoolean }) isExpanded = false;
+    @Input({ transform: toBoolean }) isExpandable = false;
+    @Input({ transform: toBoolean }) showFirstTitle = false;
+    @Input({ required: true }) columnIndex!: number;
+    @Input({ required: true }) rowIndex!: number;
+    @Input({ required: true }) column!: string | number;
+    @Input({ required: true }) contextId!: number;
+    @Input({ required: true }) header!: string;
+    @Input({ required: true }) source!: string;
     @Output() onToggleRow: EventEmitter<number> = new EventEmitter();
 
     isString = (value: unknown): boolean => CoreReportBuilder.isString(value);
diff --git a/src/core/features/reportbuilder/components/report-detail/report-detail.ts b/src/core/features/reportbuilder/components/report-detail/report-detail.ts
index 5be288272..45073ea05 100644
--- a/src/core/features/reportbuilder/components/report-detail/report-detail.ts
+++ b/src/core/features/reportbuilder/components/report-detail/report-detail.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
 import { CoreError } from '@classes/errors/error';
 import {
@@ -40,8 +41,8 @@ import { map } from 'rxjs/operators';
 })
 export class CoreReportBuilderReportDetailComponent implements OnInit {
 
-    @Input() reportId!: string;
-    @Input() isBlock = true;
+    @Input({ required: true }) reportId!: string;
+    @Input({ transform: toBoolean }) isBlock = true;
     @Input() perPage?: number;
     @Input() layout: 'card' | 'table' | 'adaptative' = 'adaptative';
     @Output() onReportLoaded = new EventEmitter<CoreReportBuilderReportDetail>();
diff --git a/src/core/features/reportbuilder/components/report-summary/report-summary.ts b/src/core/features/reportbuilder/components/report-summary/report-summary.ts
index 13b3650c5..7db580ef8 100644
--- a/src/core/features/reportbuilder/components/report-summary/report-summary.ts
+++ b/src/core/features/reportbuilder/components/report-summary/report-summary.ts
@@ -31,7 +31,7 @@ import { ModalController } from '@singletons';
 })
 export class CoreReportBuilderReportSummaryComponent implements OnInit {
 
-    @Input() reportDetail!: CoreReportBuilderReportDetail;
+    @Input({ required: true }) reportDetail!: CoreReportBuilderReportDetail;
     reportUrl!: string;
     reportDetailToDisplay!: { title: string; text: string }[];
 
diff --git a/src/core/features/search/components/global-search-filters/global-search-filters.component.ts b/src/core/features/search/components/global-search-filters/global-search-filters.component.ts
index dbd15dc0a..c956f216e 100644
--- a/src/core/features/search/components/global-search-filters/global-search-filters.component.ts
+++ b/src/core/features/search/components/global-search-filters/global-search-filters.component.ts
@@ -24,6 +24,7 @@ import { CoreEvents } from '@singletons/events';
 import { ModalController } from '@singletons';
 import { CoreUtils } from '@services/utils/utils';
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 
 type Filter<T=unknown> = T & { checked: boolean };
 
@@ -43,7 +44,7 @@ export class CoreSearchGlobalSearchFiltersComponent implements OnInit {
     allCourses: boolean | null = true;
     courses: Filter<CoreEnrolledCourseData>[] = [];
 
-    @Input() hideCourses?: boolean;
+    @Input({ transform: toBoolean }) hideCourses = false;
     @Input() filters?: CoreSearchGlobalSearchFilters;
 
     private newFilters: CoreSearchGlobalSearchFilters = {};
diff --git a/src/core/features/search/components/global-search-result/global-search-result.ts b/src/core/features/search/components/global-search-result/global-search-result.ts
index 7e60f15e6..62a215561 100644
--- a/src/core/features/search/components/global-search-result/global-search-result.ts
+++ b/src/core/features/search/components/global-search-result/global-search-result.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
 import { CoreSearchGlobalSearchResult, CoreSearchGlobalSearchResultContext } from '@features/search/services/global-search';
 
@@ -22,8 +23,8 @@ import { CoreSearchGlobalSearchResult, CoreSearchGlobalSearchResultContext } fro
 })
 export class CoreSearchGlobalSearchResultComponent implements OnChanges {
 
-    @Input() result!: CoreSearchGlobalSearchResult;
-    @Input() showCourse?: boolean;
+    @Input({ required: true }) result!: CoreSearchGlobalSearchResult;
+    @Input({ transform: toBoolean }) showCourse = true;
 
     renderedContext: CoreSearchGlobalSearchResultContext | null = null;
     renderedIcon: string | null = null;
@@ -46,7 +47,7 @@ export class CoreSearchGlobalSearchResultComponent implements OnChanges {
     private computeRenderedContext(): CoreSearchGlobalSearchResultContext | null {
         const context = { ...this.result.context } ?? {};
 
-        if (this.showCourse === false) {
+        if (!this.showCourse) {
             delete context.courseName;
         }
 
diff --git a/src/core/features/search/components/search-box/search-box.ts b/src/core/features/search/components/search-box/search-box.ts
index 1db5989c8..6494339ec 100644
--- a/src/core/features/search/components/search-box/search-box.ts
+++ b/src/core/features/search/components/search-box/search-box.ts
@@ -15,11 +15,11 @@
 import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
 
 import { CoreSites } from '@services/sites';
-import { CoreUtils } from '@services/utils/utils';
 import { CoreSearchHistory } from '../../services/search-history.service';
 import { Translate } from '@singletons';
 import { CoreSearchHistoryDBRecord } from '../../services/search-history-db';
 import { CoreForms } from '@singletons/form';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display a "search box".
@@ -41,11 +41,11 @@ export class CoreSearchBoxComponent implements OnInit {
     @Input() searchLabel?: string; // Label to be used on action button.
     @Input() placeholder?: string; // Placeholder text for search text input.
     @Input() autocorrect = 'on'; // Enables/disable Autocorrection on search text input.
-    @Input() spellcheck: string | boolean = true; // Enables/disable Spellchecker on search text input.
-    @Input() autoFocus: string | boolean = false; // Enables/disable Autofocus when entering view.
+    @Input({ transform: toBoolean }) spellcheck = true; // Enables/disable Spellchecker on search text input.
+    @Input({ transform: toBoolean }) autoFocus = false; // Enables/disable Autofocus when entering view.
     @Input() lengthCheck = 3; // Check value length before submit. If 0, any string will be submitted.
-    @Input() showClear = true; // Show/hide clear button.
-    @Input() disabled = false; // Disables the input text.
+    @Input({ transform: toBoolean }) showClear = true; // Show/hide clear button.
+    @Input({ transform: toBoolean }) disabled = false; // Disables the input text.
     @Input() initialSearch = ''; // Initial search text.
 
     /* If provided. It will save and display a history of searches for this particular Id.
@@ -72,8 +72,6 @@ export class CoreSearchBoxComponent implements OnInit {
     ngOnInit(): void {
         this.searchLabel = this.searchLabel || Translate.instant('core.search');
         this.placeholder = this.placeholder || Translate.instant('core.search');
-        this.spellcheck = CoreUtils.isTrueOrOne(this.spellcheck);
-        this.showClear = CoreUtils.isTrueOrOne(this.showClear);
         this.searchText = this.initialSearch;
 
         if (this.searchArea) {
diff --git a/src/core/features/settings/pages/about/about.html b/src/core/features/settings/pages/about/about.html
index ce17902b3..83002f367 100644
--- a/src/core/features/settings/pages/about/about.html
+++ b/src/core/features/settings/pages/about/about.html
@@ -17,12 +17,12 @@
             <ion-icon name="far-copyright" slot="start" aria-hidden="true" />
             <ion-label>{{ 'core.settings.opensourcelicenses' | translate }}</ion-label>
         </ion-item>
-        <ion-item *ngIf="privacyPolicy" class="ion-text-wrap" button core-link auto-login="no" [href]="privacyPolicy" [detail]="true"
+        <ion-item *ngIf="privacyPolicy" class="ion-text-wrap" button core-link [autoLogin]="false" [href]="privacyPolicy" [detail]="true"
             detailIcon="open-outline">
             <ion-icon name="fas-user-shield" slot="start" aria-hidden="true" />
             <ion-label>{{ 'core.settings.privacypolicy' | translate }}</ion-label>
         </ion-item>
-        <ion-item *ngIf="a11yStatement" class="ion-text-wrap" button core-link auto-login="no" [href]="a11yStatement" [detail]="true"
+        <ion-item *ngIf="a11yStatement" class="ion-text-wrap" button core-link [autoLogin]="false" [href]="a11yStatement" [detail]="true"
             detailIcon="open-outline">
             <ion-icon name="fas-universal-access" slot="start" aria-hidden="true" />
             <ion-label>{{ 'core.settings.accessstatement' | translate }}</ion-label>
@@ -32,8 +32,8 @@
             <ion-icon name="fas-bullhorn" slot="start" aria-hidden="true" />
             <ion-label>{{ 'core.settings.helpusimprove' | translate }}</ion-label>
         </ion-item>
-        <ion-item *ngIf="legalDisclaimer" class="ion-text-wrap" button core-link auto-login="no" [href]="legalDisclaimer" [detail]="true"
-            detailIcon="open-outline">
+        <ion-item *ngIf="legalDisclaimer" class="ion-text-wrap" button core-link [autoLogin]="false" [href]="legalDisclaimer"
+            [detail]="true" detailIcon="open-outline">
             <ion-icon name="fas-scale-balanced" slot="start" aria-hidden="true" />
             <ion-label>{{ 'core.settings.legaldisclaimer' | translate }}</ion-label>
         </ion-item>
diff --git a/src/core/features/settings/pages/deviceinfo/deviceinfo.html b/src/core/features/settings/pages/deviceinfo/deviceinfo.html
index 08886bdc5..1eedc62d5 100644
--- a/src/core/features/settings/pages/deviceinfo/deviceinfo.html
+++ b/src/core/features/settings/pages/deviceinfo/deviceinfo.html
@@ -42,7 +42,7 @@
                 <p class="item-heading">{{ 'core.settings.siteinfo' | translate }}<ng-container *ngIf="deviceInfo.isPrefixedUrl">
                         *</ng-container>
                 </p>
-                <p *ngIf="displaySiteUrl"><a [href]="deviceInfo.siteUrl" core-link auto-login="yes">{{ deviceInfo.siteUrl }}</a></p>
+                <p *ngIf="displaySiteUrl"><a [href]="deviceInfo.siteUrl" core-link>{{ deviceInfo.siteUrl }}</a></p>
                 <p *ngIf="deviceInfo.siteVersion">{{ deviceInfo.siteVersion }}</p>
                 <p *ngIf="deviceInfo.siteId">{{ deviceInfo.siteId }}</p>
             </ion-label>
@@ -51,7 +51,7 @@
             <ion-label class="ion-text-wrap">
                 <p class="item-heading">{{ 'core.settings.filesystemroot' | translate }}</p>
                 <p>
-                    <a *ngIf="fsClickable" [href]="deviceInfo.fileSystemRoot" core-link auto-login="no">
+                    <a *ngIf="fsClickable" [href]="deviceInfo.fileSystemRoot" core-link [autoLogin]="false">
                         {{ deviceInfo.fileSystemRoot }}
                     </a>
                 </p>
diff --git a/src/core/features/settings/pages/licenses/licenses.html b/src/core/features/settings/pages/licenses/licenses.html
index 7710faa0f..eacf71709 100644
--- a/src/core/features/settings/pages/licenses/licenses.html
+++ b/src/core/features/settings/pages/licenses/licenses.html
@@ -14,30 +14,30 @@
         <ion-searchbar [(ngModel)]="textFilter" (ionInput)="filterChanged($event.target)" (ionCancel)="filterChanged($event.target)"
             [placeholder]="'core.filter' | translate" class="ion-margin-vertical" />
         <ion-list>
-            <ion-item button *ngIf="error" class="ion-text-wrap" [href]="licensesUrl" core-link auto-login="no" [detail]="false">
+            <ion-item button *ngIf="error" class="ion-text-wrap" [href]="licensesUrl" core-link [autoLogin]="false" [detail]="false">
                 <ion-label>
                     {{ 'core.settings.opensourcelicenses' | translate }}
                 </ion-label>
-                <ion-button [href]="licensesUrl" target="_blank" fill="clear" slot="end" core-link auto-login="no">
+                <ion-button [href]="licensesUrl" target="_blank" fill="clear" slot="end" core-link [autoLogin]="false">
                     {{ 'core.view' | translate }}</ion-button>
             </ion-item>
             <ng-container *ngIf="!error">
                 <ion-item *ngFor="let license of licenses" class="ion-text-wrap" lines="inset">
                     <ion-label>
                         <p class="item-heading">
-                            <a *ngIf="license.repository" [href]="license.repository" core-link auto-login="no">{{ license.name }}</a>
+                            <a *ngIf="license.repository" [href]="license.repository" core-link [autoLogin]="false">{{ license.name }}</a>
                             <ng-container *ngIf="!license.repository">{{ license.name }}</ng-container> - {{ license.version }}
                         </p>
                         <p *ngIf="license.publisher">
                             {{ 'core.settings.publisher' | translate }}{{ 'core.labelsep' | translate }} {{ license.publisher }}
                         </p>
                         <p>{{ 'core.settings.license' | translate }}{{ 'core.labelsep' | translate }} {{ license.licenses }}</p>
-                        <p><a *ngIf="license.url" [href]="license.url" core-link auto-login="no">{{ license.url }}</a></p>
-                        <p><a *ngIf="license.email" [href]="'mailto:' + license.email" core-link auto-login="no"
+                        <p><a *ngIf="license.url" [href]="license.url" core-link [autoLogin]="false">{{ license.url }}</a></p>
+                        <p><a *ngIf="license.email" [href]="'mailto:' + license.email" core-link [autoLogin]="false"
                                 [showBrowserWarning]="false">{{ license.email }}</a></p>
                     </ion-label>
                     <ion-button *ngIf="license.licenseUrl" [href]="license.licenseUrl" target="_blank" fill="clear" slot="end" core-link
-                        auto-login="no" [ariaLabel]="'core.view' | translate">
+                        [autoLogin]="false" [ariaLabel]="'core.view' | translate">
                         <ion-icon slot="icon-only" name="fas-up-right-from-square" aria-hidden="true" />
                     </ion-button>
                 </ion-item>
diff --git a/src/core/features/sharedfiles/components/list-modal/list-modal.ts b/src/core/features/sharedfiles/components/list-modal/list-modal.ts
index f084b0cec..6138c50fd 100644
--- a/src/core/features/sharedfiles/components/list-modal/list-modal.ts
+++ b/src/core/features/sharedfiles/components/list-modal/list-modal.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, OnInit, Input } from '@angular/core';
 import { FileEntry } from '@awesome-cordova-plugins/file/ngx';
 
@@ -36,10 +37,10 @@ export class CoreSharedFilesListModalComponent implements OnInit {
 
     @Input() siteId?: string;
     @Input() mimetypes?: string[];
-    @Input() manage?: boolean;
-    @Input() pick?: boolean; // To pick a file you MUST use a modal.
+    @Input({ transform: toBoolean }) manage = false;
+    @Input({ transform: toBoolean }) pick = false; // To pick a file you MUST use a modal.
     @Input() path?: string;
-    @Input() hideSitePicker?: boolean;
+    @Input({ transform: toBoolean }) hideSitePicker = false;
 
     title?: string;
 
diff --git a/src/core/features/sharedfiles/components/list/list.ts b/src/core/features/sharedfiles/components/list/list.ts
index 221a04295..c35c1944d 100644
--- a/src/core/features/sharedfiles/components/list/list.ts
+++ b/src/core/features/sharedfiles/components/list/list.ts
@@ -21,6 +21,7 @@ import { CoreNavigator } from '@services/navigator';
 import { CoreSites } from '@services/sites';
 import { CoreEventObserver, CoreEvents } from '@singletons/events';
 import { CorePath } from '@singletons/path';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component to display the list of shared files, either as a modal or inside a page.
@@ -33,11 +34,11 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy {
 
     @Input() siteId?: string;
     @Input() mimetypes?: string[];
-    @Input() isModal?: boolean; // Whether the component is loaded in a modal.
-    @Input() manage?: boolean;
-    @Input() pick?: boolean; // To pick a file you MUST use a modal.
+    @Input({ transform: toBoolean }) isModal = false; // Whether the component is loaded in a modal.
+    @Input({ transform: toBoolean }) manage = false;
+    @Input({ transform: toBoolean }) pick = false; // To pick a file you MUST use a modal.
     @Input() path?: string;
-    @Input() showSitePicker?: boolean;
+    @Input({ transform: toBoolean }) showSitePicker = false;
     @Output() onPathChanged = new EventEmitter<string>();
     @Output() onFilePicked = new EventEmitter<FileEntry>();
 
diff --git a/src/core/features/siteplugins/classes/call-ws-click-directive.ts b/src/core/features/siteplugins/classes/call-ws-click-directive.ts
index e09d7fe0e..43f4b8bd2 100644
--- a/src/core/features/siteplugins/classes/call-ws-click-directive.ts
+++ b/src/core/features/siteplugins/classes/call-ws-click-directive.ts
@@ -16,10 +16,10 @@ import { Input, OnInit, ElementRef, Directive } from '@angular/core';
 
 import { CoreDomUtils } from '@services/utils/dom';
 import { CoreTextUtils } from '@services/utils/text';
-import { CoreUtils } from '@services/utils/utils';
 import { Translate } from '@singletons';
 import { CoreSitePluginsPluginContentComponent } from '../components/plugin-content/plugin-content';
 import { CoreSitePluginsCallWSBaseDirective } from './call-ws-directive';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Base class for directives to call a WS when the element is clicked.
@@ -30,7 +30,7 @@ import { CoreSitePluginsCallWSBaseDirective } from './call-ws-directive';
 export class CoreSitePluginsCallWSOnClickBaseDirective extends CoreSitePluginsCallWSBaseDirective implements OnInit {
 
     @Input() confirmMessage?: string; // Message to confirm the action. If not supplied, no confirmation. If empty, default message.
-    @Input() showError?: boolean | string; // Whether to show an error message if the WS call fails. Defaults to true.
+    @Input({ transform: toBoolean }) showError = true; // Whether to show an error message if the WS call fails.
 
     constructor(
         element: ElementRef,
@@ -72,7 +72,7 @@ export class CoreSitePluginsCallWSOnClickBaseDirective extends CoreSitePluginsCa
         try {
             await super.callWS();
         } catch (error) {
-            if (this.showError === undefined || CoreUtils.isTrueOrOne(this.showError)) {
+            if (this.showError) {
                 CoreDomUtils.showErrorModalDefault(
                     error,
                     Translate.instant('core.serverconnection', {
diff --git a/src/core/features/siteplugins/classes/call-ws-directive.ts b/src/core/features/siteplugins/classes/call-ws-directive.ts
index f17b7d8a4..ead49d373 100644
--- a/src/core/features/siteplugins/classes/call-ws-directive.ts
+++ b/src/core/features/siteplugins/classes/call-ws-directive.ts
@@ -27,7 +27,7 @@ import { CoreFormFields, CoreForms } from '@singletons/form';
 @Directive()
 export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy {
 
-    @Input() name!: string; // The name of the WS to call.
+    @Input({ required: true }) name!: string; // The name of the WS to call.
     @Input() params?: Record<string, unknown>; // The params for the WS call.
     @Input() preSets?: CoreSiteWSPreSets; // The preSets for the WS call.
     @Input() useOtherDataForWS?: string[] | unknown; // Whether to include other data in the params for the WS.
diff --git a/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts b/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts
index 213f4361a..26050d2d6 100644
--- a/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts
+++ b/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts
@@ -17,6 +17,7 @@ import { Component, OnInit, Input } from '@angular/core';
 import { AddonModAssignAssign, AddonModAssignPlugin, AddonModAssignSubmission } from '@addons/mod/assign/services/assign';
 import { AddonModAssignFeedbackDelegate } from '@addons/mod/assign/services/feedback-delegate';
 import { CoreSitePluginsCompileInitComponent } from '@features/siteplugins/classes/compile-init-component';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays an assign feedback plugin created using a site plugin.
@@ -28,13 +29,13 @@ import { CoreSitePluginsCompileInitComponent } from '@features/siteplugins/class
 })
 export class CoreSitePluginsAssignFeedbackComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
 
-    @Input() assign!: AddonModAssignAssign; // The assignment.
-    @Input() submission!: AddonModAssignSubmission; // The submission.
-    @Input() plugin!: AddonModAssignPlugin; // The plugin object.
-    @Input() userId!: number; // The user ID of the submission.
+    @Input({ required: true }) assign!: AddonModAssignAssign; // The assignment.
+    @Input({ required: true }) submission!: AddonModAssignSubmission; // The submission.
+    @Input({ required: true }) plugin!: AddonModAssignPlugin; // The plugin object.
+    @Input({ required: true }) userId!: number; // The user ID of the submission.
     @Input() configs?: Record<string,string>; // The configs for the plugin.
-    @Input() canEdit = false; // Whether the user can edit.
-    @Input() edit = false; // Whether the user is editing.
+    @Input({ transform: toBoolean }) canEdit = false; // Whether the user can edit.
+    @Input({ transform: toBoolean }) edit = false; // Whether the user is editing.
 
     /**
      * @inheritdoc
diff --git a/src/core/features/siteplugins/components/assign-submission/assign-submission.ts b/src/core/features/siteplugins/components/assign-submission/assign-submission.ts
index ed7232587..6fdf3d7be 100644
--- a/src/core/features/siteplugins/components/assign-submission/assign-submission.ts
+++ b/src/core/features/siteplugins/components/assign-submission/assign-submission.ts
@@ -17,6 +17,7 @@ import { Component, OnInit, Input } from '@angular/core';
 import { AddonModAssignAssign, AddonModAssignPlugin, AddonModAssignSubmission } from '@addons/mod/assign/services/assign';
 import { AddonModAssignSubmissionDelegate } from '@addons/mod/assign/services/submission-delegate';
 import { CoreSitePluginsCompileInitComponent } from '@features/siteplugins/classes/compile-init-component';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays an assign submission plugin created using a site plugin.
@@ -28,12 +29,12 @@ import { CoreSitePluginsCompileInitComponent } from '@features/siteplugins/class
 })
 export class CoreSitePluginsAssignSubmissionComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
 
-    @Input() assign!: AddonModAssignAssign; // The assignment.
-    @Input() submission!: AddonModAssignSubmission; // The submission.
-    @Input() plugin!: AddonModAssignPlugin; // The plugin object.
+    @Input({ required: true }) assign!: AddonModAssignAssign; // The assignment.
+    @Input({ required: true }) submission!: AddonModAssignSubmission; // The submission.
+    @Input({ required: true }) plugin!: AddonModAssignPlugin; // The plugin object.
     @Input() configs?: Record<string, string>; // The configs for the plugin.
-    @Input() edit = false; // Whether the user is editing.
-    @Input() allowOffline = false; // Whether to allow offline.
+    @Input({ transform: toBoolean }) edit = false; // Whether the user is editing.
+    @Input({ transform: toBoolean }) allowOffline = false; // Whether to allow offline.
 
     /**
      * @inheritdoc
diff --git a/src/core/features/siteplugins/components/module-index/module-index.ts b/src/core/features/siteplugins/components/module-index/module-index.ts
index 6d6291235..49f51aca9 100644
--- a/src/core/features/siteplugins/components/module-index/module-index.ts
+++ b/src/core/features/siteplugins/components/module-index/module-index.ts
@@ -41,8 +41,8 @@ import { CoreSitePluginsPluginContentComponent, CoreSitePluginsPluginContentLoad
 })
 export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, CoreCourseModuleMainComponent {
 
-    @Input() module!: CoreCourseModuleData; // The module.
-    @Input() courseId!: number; // Course ID the module belongs to.
+    @Input({ required: true }) module!: CoreCourseModuleData; // The module.
+    @Input({ required: true }) courseId!: number; // Course ID the module belongs to.
     @Input() pageTitle?: string; // Current page title. It can be used by the "new-content" directives.
 
     @ViewChild(CoreSitePluginsPluginContentComponent) content?: CoreSitePluginsPluginContentComponent;
diff --git a/src/core/features/siteplugins/components/plugin-content/plugin-content.ts b/src/core/features/siteplugins/components/plugin-content/plugin-content.ts
index 49634c4a9..6a501b8d8 100644
--- a/src/core/features/siteplugins/components/plugin-content/plugin-content.ts
+++ b/src/core/features/siteplugins/components/plugin-content/plugin-content.ts
@@ -50,7 +50,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
     @ViewChild('compile') compileComponent?: CoreCompileHtmlComponent;
 
     @HostBinding('class') @Input() component = '';
-    @Input() method!: string;
+    @Input({ required: true }) method!: string;
     @Input() args?: Record<string, unknown>;
     @Input() initResult?: CoreSitePluginsContent | null; // Result of the init WS call of the handler.
     @Input() data: Record<string, unknown> = {}; // Data to pass to the component.
diff --git a/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts b/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts
index 8eed77776..382051be7 100644
--- a/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts
+++ b/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
 
 import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate';
@@ -33,11 +34,11 @@ export class CoreSitePluginsQuestionBehaviourComponent extends CoreSitePluginsCo
     @Input() component?: string; // The component the question belongs to.
     @Input() componentId?: number; // ID of the component the question belongs to.
     @Input() attemptId?: number; // Attempt ID.
-    @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
+    @Input({ transform: toBoolean }) offlineEnabled = false; // Whether the question can be answered in offline.
     @Input() contextLevel?: ContextLevel; // The context level.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters.
-    @Input() review?: boolean; // Whether the user is in review mode.
+    @Input({ transform: toBoolean }) review = false; // Whether the user is in review mode.
     @Input() preferredBehaviour?: string; // Preferred behaviour.
     @Output() buttonClicked = new EventEmitter<CoreQuestionBehaviourButton>(); // Will emit when a behaviour button is clicked.
     @Output() onAbort = new EventEmitter<void>(); // Should emit an event if the question should be aborted.
@@ -58,6 +59,8 @@ export class CoreSitePluginsQuestionBehaviourComponent extends CoreSitePluginsCo
         this.jsData.offlineEnabled = this.offlineEnabled;
         this.jsData.contextLevel = this.contextLevel;
         this.jsData.contextInstanceId = this.contextInstanceId;
+        this.jsData.courseId = this.courseId;
+        this.jsData.review = this.review;
         this.jsData.buttonClicked = this.buttonClicked;
         this.jsData.onAbort = this.onAbort;
 
diff --git a/src/core/features/siteplugins/components/question/question.ts b/src/core/features/siteplugins/components/question/question.ts
index 8c9bd7a39..686080ced 100644
--- a/src/core/features/siteplugins/components/question/question.ts
+++ b/src/core/features/siteplugins/components/question/question.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
 
 import { AddonModQuizQuestion } from '@features/question/classes/base-question-component';
@@ -34,11 +35,11 @@ export class CoreSitePluginsQuestionComponent extends CoreSitePluginsCompileInit
     @Input() component?: string; // The component the question belongs to.
     @Input() componentId?: number; // ID of the component the question belongs to.
     @Input() attemptId?: number; // Attempt ID.
-    @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
+    @Input({ transform: toBoolean }) offlineEnabled = false; // Whether the question can be answered in offline.
     @Input() contextLevel?: ContextLevel; // The context level.
     @Input() contextInstanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters.
-    @Input() review?: boolean; // Whether the user is in review mode.
+    @Input({ transform: toBoolean }) review = false; // Whether the user is in review mode.
     @Input() preferredBehaviour?: string; // Preferred behaviour.
     @Output() buttonClicked = new EventEmitter<CoreQuestionBehaviourButton>(); // Will emit when a behaviour button is clicked.
     @Output() onAbort = new EventEmitter<void>(); // Should emit an event if the question should be aborted.
diff --git a/src/core/features/siteplugins/components/quiz-access-rule/quiz-access-rule.ts b/src/core/features/siteplugins/components/quiz-access-rule/quiz-access-rule.ts
index 7c4c2a502..a030e888c 100644
--- a/src/core/features/siteplugins/components/quiz-access-rule/quiz-access-rule.ts
+++ b/src/core/features/siteplugins/components/quiz-access-rule/quiz-access-rule.ts
@@ -18,6 +18,7 @@ import { FormGroup } from '@angular/forms';
 import { AddonModQuizAccessRuleDelegate } from '@addons/mod/quiz/services/access-rules-delegate';
 import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz';
 import { CoreSitePluginsCompileInitComponent } from '@features/siteplugins/classes/compile-init-component';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Component that displays a quiz access rule created using a site plugin.
@@ -32,7 +33,7 @@ export class CoreSitePluginsQuizAccessRuleComponent extends CoreSitePluginsCompi
     @Input() rule?: string; // The name of the rule.
     @Input() quiz?: AddonModQuizQuizWSData; // The quiz the rule belongs to.
     @Input() attempt?: AddonModQuizAttemptWSData; // The attempt being started/continued.
-    @Input() prefetch?: boolean; // Whether the user is prefetching the quiz.
+    @Input({ transform: toBoolean }) prefetch = false; // Whether the user is prefetching the quiz.
     @Input() siteId?: string; // Site ID.
     @Input() form?: FormGroup; // Form where to add the form control.
 
diff --git a/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts b/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts
index f67f48021..17a804768 100644
--- a/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts
+++ b/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, OnInit, Input } from '@angular/core';
 import { FormGroup } from '@angular/forms';
 
@@ -32,9 +33,9 @@ import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profi
 export class CoreSitePluginsUserProfileFieldComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
 
     @Input() field?: AuthEmailSignupProfileField | CoreUserProfileField; // The profile field to be rendered.
-    @Input() signup = false; // True if editing the field in signup. Defaults to false.
-    @Input() edit = false; // True if editing the field. Defaults to false.
-    @Input() disabled = false; // True if disabled. Defaults to false.
+    @Input({ transform: toBoolean }) signup = false; // True if editing the field in signup.
+    @Input({ transform: toBoolean }) edit = false; // True if editing the field.
+    @Input({ transform: toBoolean }) disabled = false; // True if disabled.
     @Input() form?: FormGroup; // Form where to add the form control. Required if edit=true or signup=true.
     @Input() registerAuth?: string; // Register auth method. E.g. 'email'.
     @Input() contextLevel?: ContextLevel; // The context level.
diff --git a/src/core/features/siteplugins/components/workshop-assessment-strategy/workshop-assessment-strategy.ts b/src/core/features/siteplugins/components/workshop-assessment-strategy/workshop-assessment-strategy.ts
index 2030ab474..db0fc6949 100644
--- a/src/core/features/siteplugins/components/workshop-assessment-strategy/workshop-assessment-strategy.ts
+++ b/src/core/features/siteplugins/components/workshop-assessment-strategy/workshop-assessment-strategy.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { toBoolean } from '@/core/transforms/boolean';
 import { AddonWorkshopAssessmentStrategyDelegate } from '@addons/mod/workshop/services/assessment-strategy-delegate';
 import { AddonModWorkshopGetAssessmentFormFieldsParsedData } from '@addons/mod/workshop/services/workshop';
 import { AddonModWorkshopSubmissionAssessmentWithFormData } from '@addons/mod/workshop/services/workshop-helper';
@@ -28,13 +29,13 @@ import { CoreSitePluginsCompileInitComponent } from '@features/siteplugins/class
 })
 export class CoreSitePluginsWorkshopAssessmentStrategyComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
 
-    @Input() workshopId!: number;
-    @Input() assessment!: AddonModWorkshopSubmissionAssessmentWithFormData;
-    @Input() edit!: boolean;
-    @Input() selectedValues!: AddonModWorkshopGetAssessmentFormFieldsParsedData[];
-    @Input() fieldErrors!: Record<string, string>;
-    @Input() strategy!: string;
-    @Input() moduleId!: number;
+    @Input({ required: true }) workshopId!: number;
+    @Input({ required: true }) assessment!: AddonModWorkshopSubmissionAssessmentWithFormData;
+    @Input({ required: true, transform: toBoolean }) edit = false;
+    @Input({ required: true }) selectedValues!: AddonModWorkshopGetAssessmentFormFieldsParsedData[];
+    @Input({ required: true }) fieldErrors!: Record<string, string>;
+    @Input({ required: true }) strategy!: string;
+    @Input({ required: true }) moduleId!: number;
     @Input() courseId?: number;
 
     /**
diff --git a/src/core/features/siteplugins/directives/call-ws-new-content.ts b/src/core/features/siteplugins/directives/call-ws-new-content.ts
index 729a8130a..7568380e4 100644
--- a/src/core/features/siteplugins/directives/call-ws-new-content.ts
+++ b/src/core/features/siteplugins/directives/call-ws-new-content.ts
@@ -15,12 +15,12 @@
 import { Directive, Input, ElementRef, Optional } from '@angular/core';
 import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
 import { CoreNavigator } from '@services/navigator';
-import { CoreUtils } from '@services/utils/utils';
 import { Md5 } from 'ts-md5';
 
 import { CoreSitePluginsCallWSOnClickBaseDirective } from '../classes/call-ws-click-directive';
 import { CoreSitePluginsPluginContentComponent } from '../components/plugin-content/plugin-content';
 import { CoreSitePlugins } from '../services/siteplugins';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Directive to call a WS when the element is clicked and load a new content passing the WS result as args. This new content
@@ -59,14 +59,14 @@ export class CoreSitePluginsCallWSNewContentDirective extends CoreSitePluginsCal
     @Input() method?: string; // The method to get the new content. If not provided, use the same method as current page.
     @Input() args?: Record<string, unknown>; // The params to get the new content.
     @Input() title?: string; // The title to display with the new content. Only if samePage=false.
-    @Input() samePage?: boolean | string; // Whether to display the content in same page or open a new one. Defaults to new page.
+    @Input({ transform: toBoolean }) samePage = false; // Whether to display the content in same page or open a new one.
     @Input() useOtherData?: string[] | unknown; // Whether to include other data in the args.
     @Input() form?: string; // ID or name to identify a form. The form data will be retrieved and sent to the WS.
     // JS variables to pass to the new page so they can be used in the template or JS.
     // If true is supplied instead of an object, all initial variables from current page will be copied.
     @Input() jsData?: Record<string, unknown> | boolean;
     @Input() newContentPreSets?: CoreSiteWSPreSets; // The preSets for the WS call of the new content.
-    @Input() ptrEnabled?: boolean | string; // Whether PTR should be enabled in the new page. Defaults to true.
+    @Input({ transform: toBoolean }) ptrEnabled = true; // Whether PTR should be enabled in the new page.
 
     constructor(
         element: ElementRef,
@@ -93,7 +93,7 @@ export class CoreSitePluginsCallWSNewContentDirective extends CoreSitePluginsCal
             jsData = this.parentContent?.data || {};
         }
 
-        if (CoreUtils.isTrueOrOne(this.samePage)) {
+        if (this.samePage) {
             // Update the parent content (if it exists).
             this.parentContent?.updateContent(args, this.component, this.method, jsData, this.newContentPreSets);
         } else {
diff --git a/src/core/features/siteplugins/directives/call-ws.ts b/src/core/features/siteplugins/directives/call-ws.ts
index fc5bc0d3a..8b29b4d6b 100644
--- a/src/core/features/siteplugins/directives/call-ws.ts
+++ b/src/core/features/siteplugins/directives/call-ws.ts
@@ -16,10 +16,10 @@ import { Directive, Input, ElementRef, Optional } from '@angular/core';
 
 import { Translate } from '@singletons';
 import { CoreToasts } from '@services/toasts';
-import { CoreUtils } from '@services/utils/utils';
 import { CoreNavigator } from '@services/navigator';
 import { CoreSitePluginsCallWSOnClickBaseDirective } from '../classes/call-ws-click-directive';
 import { CoreSitePluginsPluginContentComponent } from '../components/plugin-content/plugin-content';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the input data:
@@ -52,8 +52,8 @@ import { CoreSitePluginsPluginContentComponent } from '../components/plugin-cont
 export class CoreSitePluginsCallWSDirective extends CoreSitePluginsCallWSOnClickBaseDirective {
 
     @Input() successMessage?: string; // Message to show on success. If not supplied, no message. If empty, default message.
-    @Input() goBackOnSuccess?: boolean | string; // Whether to go back if the WS call is successful.
-    @Input() refreshOnSuccess?: boolean | string; // Whether to refresh the current view if the WS call is successful.
+    @Input({ transform: toBoolean }) goBackOnSuccess = false; // Whether to go back if the WS call is successful.
+    @Input({ transform: toBoolean }) refreshOnSuccess = false; // Whether to refresh the current view if the WS call is successful.
 
     constructor(
         element: ElementRef,
@@ -66,9 +66,9 @@ export class CoreSitePluginsCallWSDirective extends CoreSitePluginsCallWSOnClick
      * @inheritdoc
      */
     protected async wsCallSuccess(): Promise<void> {
-        if (CoreUtils.isTrueOrOne(this.goBackOnSuccess)) {
+        if (this.goBackOnSuccess) {
             await CoreNavigator.back();
-        } else if (CoreUtils.isTrueOrOne(this.refreshOnSuccess) && this.parentContent) {
+        } else if (this.refreshOnSuccess && this.parentContent) {
             this.parentContent.refreshContent(true);
         }
 
diff --git a/src/core/features/siteplugins/directives/new-content.ts b/src/core/features/siteplugins/directives/new-content.ts
index a22d57e2c..31d207fbb 100644
--- a/src/core/features/siteplugins/directives/new-content.ts
+++ b/src/core/features/siteplugins/directives/new-content.ts
@@ -17,10 +17,10 @@ import { Md5 } from 'ts-md5';
 
 import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
 import { CoreNavigator } from '@services/navigator';
-import { CoreUtils } from '@services/utils/utils';
 import { CoreSitePluginsPluginContentComponent } from '../components/plugin-content/plugin-content';
 import { CoreSitePlugins } from '../services/siteplugins';
 import { CoreForms } from '@singletons/form';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Directive to display a new site plugin content when clicked. This new content can be displayed in a new page or in the
@@ -51,14 +51,14 @@ export class CoreSitePluginsNewContentDirective implements OnInit {
     @Input() method?: string; // The method to get the new content. If not provided, use the same method as current page.
     @Input() args?: Record<string, unknown>; // The params to get the new content.
     @Input() title?: string; // The title to display with the new content. Only if samePage=false.
-    @Input() samePage?: boolean | string; // Whether to display the content in same page or open a new one. Defaults to new page.
+    @Input({ transform: toBoolean }) samePage = false; // Whether to display the content in same page or open a new one.
     @Input() useOtherData?: string[] | unknown; // Whether to include other data in the args.
     @Input() form?: string; // ID or name to identify a form. The form data will be retrieved and sent to the WS.
     // JS variables to pass to the new page so they can be used in the template or JS.
     // If true is supplied instead of an object, all initial variables from current page will be copied.
     @Input() jsData?: Record<string, unknown> | boolean;
     @Input() preSets?: CoreSiteWSPreSets; // The preSets for the WS call of the new content.
-    @Input() ptrEnabled?: boolean | string; // Whether PTR should be enabled in the new page. Defaults to true.
+    @Input({ transform: toBoolean }) ptrEnabled = true; // Whether PTR should be enabled in the new page.
 
     protected element: HTMLElement;
 
@@ -92,7 +92,7 @@ export class CoreSitePluginsNewContentDirective implements OnInit {
                 jsData = this.parentContent?.data || {};
             }
 
-            if (CoreUtils.isTrueOrOne(this.samePage)) {
+            if (this.samePage) {
                 // Update the parent content (if it exists).
                 this.parentContent?.updateContent(args, this.component, this.method, jsData, this.preSets);
             } else {
diff --git a/src/core/features/user/classes/base-profilefield-component.ts b/src/core/features/user/classes/base-profilefield-component.ts
index ae8edbfe6..2c48f0f2d 100644
--- a/src/core/features/user/classes/base-profilefield-component.ts
+++ b/src/core/features/user/classes/base-profilefield-component.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input, OnInit } from '@angular/core';
 import { FormGroup, Validators, FormControl } from '@angular/forms';
 
@@ -28,9 +29,9 @@ import { CoreUserProfileField } from '@features/user/services/user';
 export abstract class CoreUserProfileFieldBaseComponent<T = string> implements OnInit {
 
     @Input() field?: AuthEmailSignupProfileField | CoreUserProfileField; // The profile field to be rendered.
-    @Input() signup = false; // True if editing the field in signup. Defaults to false.
-    @Input() edit = false; // True if editing the field. Defaults to false.
-    @Input() disabled = false; // True if disabled. Defaults to false.
+    @Input({ transform: toBoolean }) signup = false; // True if editing the field in signup.
+    @Input({ transform: toBoolean }) edit = false; // True if editing the field.
+    @Input({ transform: toBoolean }) disabled = false; // True if disabled.
     @Input() form?: FormGroup; // Form where to add the form control. Required if edit=true or signup=true.
     @Input() registerAuth?: string; // Register auth method. E.g. 'email'.
     @Input() contextLevel?: ContextLevel; // The context level.
diff --git a/src/core/features/user/components/user-profile-field/user-profile-field.ts b/src/core/features/user/components/user-profile-field/user-profile-field.ts
index c3b4e82e5..9a33ee6ed 100644
--- a/src/core/features/user/components/user-profile-field/user-profile-field.ts
+++ b/src/core/features/user/components/user-profile-field/user-profile-field.ts
@@ -20,6 +20,7 @@ import { CoreUserProfileField } from '@features/user/services/user';
 import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate';
 import { CoreUtils } from '@services/utils/utils';
 import { ContextLevel } from '@/core/constants';
+import { toBoolean } from '@/core/transforms/boolean';
 
 /**
  * Directive to render user profile field.
@@ -31,8 +32,8 @@ import { ContextLevel } from '@/core/constants';
 export class CoreUserProfileFieldComponent implements OnInit {
 
     @Input() field?: AuthEmailSignupProfileField | CoreUserProfileField; // The profile field to be rendered.
-    @Input() signup = false; // True if editing the field in signup. Defaults to false.
-    @Input() edit = false; // True if editing the field. Defaults to false.
+    @Input({ transform: toBoolean }) signup = false; // True if editing the field in signup.
+    @Input({ transform: toBoolean }) edit = false; // True if editing the field.
     @Input() form?: FormGroup; // Form where to add the form control. Required if edit=true or signup=true.
     @Input() registerAuth?: string; // Register auth method. E.g. 'email'.
     @Input() contextLevel?: ContextLevel; // The context level.
@@ -57,13 +58,13 @@ export class CoreUserProfileFieldComponent implements OnInit {
         }
 
         this.data.field = this.field;
-        this.data.edit = CoreUtils.isTrueOrOne(this.edit);
+        this.data.edit = this.edit;
         this.data.contextLevel = this.contextLevel;
         this.data.contextInstanceId = this.contextInstanceId;
         this.data.courseId = this.courseId;
 
         if (this.edit) {
-            this.data.signup = CoreUtils.isTrueOrOne(this.signup);
+            this.data.signup = this.signup;
             this.data.disabled = 'locked' in this.field && CoreUtils.isTrueOrOne(this.field.locked);
             this.data.form = this.form;
             this.data.registerAuth = this.registerAuth;
diff --git a/src/core/features/user/pages/about/about.html b/src/core/features/user/pages/about/about.html
index f068fdc3e..7d7d43007 100644
--- a/src/core/features/user/pages/about/about.html
+++ b/src/core/features/user/pages/about/about.html
@@ -44,7 +44,7 @@
                 <ion-item class="ion-text-wrap" *ngIf="user.email">
                     <ion-label>
                         <p class="item-heading">{{ 'core.user.email' | translate }}</p>
-                        <p><a class="core-anchor" href="mailto:{{user.email}}" core-link auto-login="no" [showBrowserWarning]="false">
+                        <p><a class="core-anchor" href="mailto:{{user.email}}" core-link [autoLogin]="false" [showBrowserWarning]="false">
                                 {{ user.email }}
                             </a></p>
                     </ion-label>
@@ -70,7 +70,7 @@
                 <ion-item class="ion-text-wrap" *ngIf="user.phone1">
                     <ion-label>
                         <p class="item-heading">{{ 'core.user.phone1' | translate}}</p>
-                        <p><a class="core-anchor" href="tel:{{user.phone1}}" core-link auto-login="no" [showBrowserWarning]="false">
+                        <p><a class="core-anchor" href="tel:{{user.phone1}}" core-link [autoLogin]="false" [showBrowserWarning]="false">
                                 {{ user.phone1 }}
                             </a></p>
                     </ion-label>
@@ -78,7 +78,7 @@
                 <ion-item class="ion-text-wrap" *ngIf="user.phone2">
                     <ion-label>
                         <p class="item-heading">{{ 'core.user.phone2' | translate}}</p>
-                        <p><a class="core-anchor" href="tel:{{user.phone2}}" core-link auto-login="no" [showBrowserWarning]="false">
+                        <p><a class="core-anchor" href="tel:{{user.phone2}}" core-link [autoLogin]="false" [showBrowserWarning]="false">
                                 {{ user.phone2 }}
                             </a></p>
                     </ion-label>
diff --git a/src/core/features/usertours/components/user-tour/user-tour.ts b/src/core/features/usertours/components/user-tour/user-tour.ts
index 2f278d8fc..0d0aacce6 100644
--- a/src/core/features/usertours/components/user-tour/user-tour.ts
+++ b/src/core/features/usertours/components/user-tour/user-tour.ts
@@ -52,9 +52,9 @@ const BACKDROP_DISMISS_SAFETY_TRESHOLD = 1000;
 })
 export class CoreUserToursUserTourComponent implements AfterViewInit, OnDestroy {
 
-    @Input() container!: HTMLElement;
-    @Input() id!: string;
-    @Input() component!: unknown;
+    @Input({ required: true }) container!: HTMLElement;
+    @Input({ required: true }) id!: string;
+    @Input({ required: true }) component!: unknown;
     @Input() componentProps?: Record<string, unknown>;
     @Input() focus?: HTMLElement;
     @Input() side?: CoreUserToursSide;
diff --git a/src/core/features/viewer/components/text/text.ts b/src/core/features/viewer/components/text/text.ts
index 37029e7ba..a7ea45cda 100644
--- a/src/core/features/viewer/components/text/text.ts
+++ b/src/core/features/viewer/components/text/text.ts
@@ -14,6 +14,7 @@
 
 import { ContextLevel } from '@/core/constants';
 import { CoreSharedModule } from '@/core/shared.module';
+import { toBoolean } from '@/core/transforms/boolean';
 import { Component, Input } from '@angular/core';
 import { CoreFileEntry } from '@services/file-helper';
 
@@ -39,11 +40,11 @@ export class CoreViewerTextComponent {
     @Input() component?: string; // Component to use in format-text.
     @Input() componentId?: string | number; // Component ID to use in format-text.
     @Input() files?: CoreFileEntry[]; // List of files.
-    @Input() filter?: boolean; // Whether to filter the text.
+    @Input({ transform: toBoolean }) filter?: boolean; // Whether to filter the text.
     @Input() contextLevel?: ContextLevel; // The context level.
     @Input() instanceId?: number; // The instance ID related to the context.
     @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters.
-    @Input() displayCopyButton?: boolean; // Whether to display a button to copy the contents.
+    @Input({ transform: toBoolean }) displayCopyButton = false; // Whether to display a button to copy the contents.
 
     /**
      * Close modal.
diff --git a/src/core/features/viewer/pages/iframe/iframe.ts b/src/core/features/viewer/pages/iframe/iframe.ts
index 5b6ef0d13..ed468be0b 100644
--- a/src/core/features/viewer/pages/iframe/iframe.ts
+++ b/src/core/features/viewer/pages/iframe/iframe.ts
@@ -31,10 +31,7 @@ export class CoreViewerIframePage implements OnInit {
     async ngOnInit(): Promise<void> {
         this.title = CoreNavigator.getRouteParam('title');
         this.url = CoreNavigator.getRouteParam('url');
-        const autoLoginParam = CoreNavigator.getRouteParam('autoLogin') ?? true;
-        this.autoLogin = typeof autoLoginParam === 'boolean' ?
-            autoLoginParam :
-            autoLoginParam !== 'no'; // Support deprecated values yes/no/check.
+        this.autoLogin = CoreNavigator.getRouteBooleanParam('autoLogin') ?? true;
     }
 
 }
diff --git a/src/core/transforms/boolean.ts b/src/core/transforms/boolean.ts
new file mode 100644
index 000000000..88fc7b961
--- /dev/null
+++ b/src/core/transforms/boolean.ts
@@ -0,0 +1,34 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * Transform an Input value to a boolean.
+ * False values are: 0, '0', 'false', false, undefined, null.
+ * Please notice that empty strings are considered true for consistency with HTML.
+ *
+ * @param value Value to transform.
+ * @returns Transformed value.
+ */
+export function toBoolean(value: unknown): boolean {
+    if (value === undefined || value === null) {
+        return false;
+    }
+
+    if (value === '') {
+        // Empty string is considered true for consistency with HTML, where putting an attribute without value means true.
+        return true;
+    }
+
+    return !(value === false || value === 'false' || Number(value) === 0);
+}