diff --git a/Cargo.toml b/Cargo.toml
index 7f44d8b6e4cf357ceb5e42cad7ca577074da1a06..7c85c9e48d06c674a7e42e3ebc33dac827a77b2a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,7 @@ exclude = ["assets/*"]
 [features]
 multicam = []
 placeholder = []
+password = []
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
diff --git a/examples/password.rs b/examples/password.rs
new file mode 100644
index 0000000000000000000000000000000000000000..28f92dcc71a508045e42abcb3a57238167152ae2
--- /dev/null
+++ b/examples/password.rs
@@ -0,0 +1,46 @@
+use bevy::prelude::*;
+use bevy_cosmic_edit::{password::Password, *};
+use util::{change_active_editor_sprite, deselect_editor_on_esc, print_editor_text};
+
+fn setup(mut commands: Commands) {
+    commands.spawn(Camera2dBundle::default());
+
+    // Sprite editor
+    commands.spawn((
+        CosmicEditBundle {
+            max_lines: CosmicMaxLines(1),
+            mode: CosmicMode::InfiniteLine,
+            sprite_bundle: SpriteBundle {
+                // Sets size of text box
+                sprite: Sprite {
+                    custom_size: Some(Vec2::new(300., 100.)),
+                    ..default()
+                },
+                // Position of text box
+                transform: Transform::from_xyz(0., 100., 0.),
+                ..default()
+            },
+            ..default()
+        },
+        Password::default(),
+    ));
+}
+
+fn main() {
+    App::new()
+        .add_plugins(DefaultPlugins)
+        .add_plugins(CosmicEditPlugin {
+            change_cursor: CursorConfig::Default,
+            ..default()
+        })
+        .add_systems(Startup, setup)
+        .add_systems(
+            Update,
+            (
+                change_active_editor_sprite,
+                deselect_editor_on_esc,
+                print_editor_text,
+            ),
+        )
+        .run();
+}
diff --git a/src/lib.rs b/src/lib.rs
index 726f5a242712ffeace3c930240f7ae0691c553c0..3028487eeae09fbf8f135bcae981e4021366b797 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -203,6 +203,9 @@ impl Default for CosmicFontConfig {
 #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
 pub struct KbInput;
 
+#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Render;
+
 /// Plugin struct that adds systems and initializes resources related to cosmic edit functionality.
 #[derive(Default)]
 pub struct CosmicEditPlugin {
@@ -248,6 +251,7 @@ impl Plugin for CosmicEditPlugin {
                 render_texture,
             )
                 .chain()
+                .in_set(Render)
                 .after(TransformSystem::TransformPropagate),
         )
         .init_resource::<FocusedWidget>()
@@ -287,6 +291,9 @@ fn add_feature_plugins(app: &mut App) -> &mut App {
     #[cfg(feature = "placeholder")]
     app.add_plugins(plugins::placeholder::PlaceholderPlugin);
 
+    #[cfg(feature = "password")]
+    app.add_plugins(plugins::password::PasswordPlugin);
+
     app
 }
 
diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs
index 2c07c75b01ca1a0c47da5a42c846fcb4bd87a1d0..8355c2fb4089375d89fad9d153c68621f4b4028a 100644
--- a/src/plugins/mod.rs
+++ b/src/plugins/mod.rs
@@ -1,2 +1,4 @@
+#[cfg(feature = "password")]
+pub mod password;
 #[cfg(feature = "placeholder")]
 pub mod placeholder;
diff --git a/src/plugins/password/mod.rs b/src/plugins/password/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..739cb809b82b3485eb0c0567c640b8a190982484
--- /dev/null
+++ b/src/plugins/password/mod.rs
@@ -0,0 +1,113 @@
+use bevy::prelude::*;
+use cosmic_text::{Buffer, Edit, Shaping};
+
+use crate::{CosmicBuffer, CosmicEditor, CosmicFontSystem, DefaultAttrs, Render};
+
+pub struct PasswordPlugin;
+
+impl Plugin for PasswordPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(
+            PostUpdate,
+            (
+                hide_password_text.before(Render),
+                restore_password_text.after(Render),
+            ),
+        );
+    }
+}
+
+#[derive(Component)]
+pub struct Password {
+    real_text: String,
+    glyph: char,
+}
+
+impl Default for Password {
+    fn default() -> Self {
+        Self {
+            real_text: Default::default(),
+            glyph: '*',
+        }
+    }
+}
+
+fn hide_password_text(
+    mut q: Query<(
+        &mut Password,
+        &mut CosmicBuffer,
+        &DefaultAttrs,
+        Option<&mut CosmicEditor>,
+    )>,
+    mut font_system: ResMut<CosmicFontSystem>,
+) {
+    for (mut password, mut buffer, attrs, editor_opt) in q.iter_mut() {
+        if let Some(mut editor) = editor_opt {
+            editor.with_buffer_mut(|buffer| {
+                fn get_text(buffer: &mut Buffer) -> String {
+                    let mut text = String::new();
+                    let line_count = buffer.lines.len();
+
+                    for (i, line) in buffer.lines.iter().enumerate() {
+                        text.push_str(line.text());
+
+                        if i < line_count - 1 {
+                            text.push('\n');
+                        }
+                    }
+
+                    text
+                }
+
+                let text = get_text(buffer);
+                buffer.set_text(
+                    &mut font_system,
+                    password.glyph.to_string().repeat(text.len()).as_str(),
+                    attrs.as_attrs(),
+                    Shaping::Advanced,
+                );
+                password.real_text = text;
+            });
+
+            continue;
+        }
+
+        let text = buffer.get_text();
+        buffer.set_text(
+            &mut font_system,
+            password.glyph.to_string().repeat(text.len()).as_str(),
+            attrs.as_attrs(),
+        );
+        password.real_text = text;
+    }
+}
+
+fn restore_password_text(
+    mut q: Query<(
+        &Password,
+        &mut CosmicBuffer,
+        &DefaultAttrs,
+        Option<&mut CosmicEditor>,
+    )>,
+    mut font_system: ResMut<CosmicFontSystem>,
+) {
+    for (password, mut buffer, attrs, editor_opt) in q.iter_mut() {
+        if let Some(mut editor) = editor_opt {
+            editor.with_buffer_mut(|buffer| {
+                buffer.set_text(
+                    &mut font_system,
+                    password.real_text.as_str(),
+                    attrs.as_attrs(),
+                    Shaping::Advanced,
+                )
+            });
+            continue;
+        }
+
+        buffer.set_text(
+            &mut font_system,
+            password.real_text.as_str(),
+            attrs.as_attrs(),
+        );
+    }
+}