diff --git a/string.c b/string.c
index 6bb8a24313ce..74a96ddf1366 100644
--- a/string.c
+++ b/string.c
@@ -9845,6 +9845,81 @@ rb_str_rpartition(VALUE str, VALUE sep)
 					RSTRING_LEN(str)-pos-RSTRING_LEN(sep)));
 }
 
+/*
+ *  call-seq:
+ *     str.before(sep)             -> str or nil
+ *     str.before(regexp)          -> str or nil
+ *
+ *  Searches sep or pattern (regexp) in the string
+ *  and returns the part before it.
+ *  If it is not found, returns nil.
+ *
+ *     "hello".before("l")         #=> "he"
+ *     "hello".before(/ll/i)       #=> "he"
+ */
+VALUE
+rb_str_before(VALUE str, VALUE sep)
+{
+    if (RSTRING_LEN(str) == 0) {
+        failed: return Qnil;
+    }
+
+    sep = get_pat_quoted(sep, 0);
+
+    if (RB_TYPE_P(sep, T_REGEXP)) {
+        rb_reg_search(sep, str, 0, 0);
+        VALUE match = rb_backref_get();
+
+        return rb_reg_match_pre(match);
+    }
+
+    long pos = rb_str_index(str, sep, 0);
+    if (pos < 0) goto failed;
+
+    return str_substr(str, 0, pos, TRUE);
+}
+
+/*
+ *  call-seq:
+ *     str.after(sep)             -> str or nil
+ *     str.after(regexp)          -> str or nil
+ *
+ *  Searches sep or pattern (regexp) in the string
+ *  and returns the part after it.
+ *  If it is not found, returns nil.
+ *
+ *     "hello".after("l")         #=> "lo"
+ *     "hello".after(/ll/i)       #=> "o"
+ */
+VALUE
+rb_str_after(VALUE str, VALUE sep)
+{
+    long len = RSTRING_LEN(str);
+
+    if (len == 0) {
+        failed: return Qnil;
+    }
+
+    sep = get_pat_quoted(sep, 0);
+
+    if (RB_TYPE_P(sep, T_REGEXP)) {
+        rb_reg_search(sep, str, 0, 0);
+        VALUE match = rb_backref_get();
+
+        return rb_reg_match_post(match);
+    }
+
+    long pos = rb_str_index(str, sep, 0);
+    if (pos < 0) goto failed;
+
+    return str_substr(
+        str,
+        pos + RSTRING_LEN(sep),
+        len - pos,
+        TRUE
+    );
+}
+
 /*
  *  call-seq:
  *     str.start_with?([prefixes]+)   -> true or false
@@ -11293,6 +11368,9 @@ Init_String(void)
     rb_define_method(rb_cString, "partition", rb_str_partition, 1);
     rb_define_method(rb_cString, "rpartition", rb_str_rpartition, 1);
 
+    rb_define_method(rb_cString, "before", rb_str_before, 1);
+    rb_define_method(rb_cString, "after", rb_str_after, 1);
+
     rb_define_method(rb_cString, "encoding", rb_obj_encoding, 0); /* in encoding.c */
     rb_define_method(rb_cString, "force_encoding", rb_str_force_encoding, 1);
     rb_define_method(rb_cString, "b", rb_str_b, 0);
diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb
index 507e067a0df8..35074459b6df 100644
--- a/test/ruby/test_string.rb
+++ b/test/ruby/test_string.rb
@@ -2627,6 +2627,34 @@ def (hyphen = Object.new).to_str; "-"; end
     assert_equal("hello", hello, bug)
   end
 
+  def test_before
+    assert_equal("hello", "hello world".before(" "))
+    assert_equal(nil, "onetwothree".before("four"))
+    assert_equal("he", "hello world".before("l"))
+    assert_equal("cat", "cats and dogs".before("s"))
+
+    assert_equal("foo", "foobarbaz".before(/bar/))
+    assert_equal(nil, "cats and dogs".before(/pigs/))
+    assert_equal("he", "hello".before(/ll/))
+    assert_equal("", "hello".before(//))
+
+    assert_equal(nil, "".before(""))
+  end
+
+  def test_after
+    assert_equal("world", "hello world".after(" "))
+    assert_equal(nil, "onetwothree".after("four"))
+    assert_equal("lo world", "hello world".after("l"))
+    assert_equal(" and dogs", "cats and dogs".after("s"))
+
+    assert_equal("baz", "foobarbaz".after(/bar/))
+    assert_equal(nil, "cats and dogs".after(/pigs/))
+    assert_equal("o", "hello".after(/ll/))
+    assert_equal("hello", "hello".after(//))
+
+    assert_equal(nil, "".after(""))
+  end
+
   def test_setter
     assert_raise(TypeError) { $/ = 1 }
     name = "\u{5206 884c}"