diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -260,6 +260,7 @@
     'PhutilPersonaAuthAdapter' => 'auth/PhutilPersonaAuthAdapter.php',
     'PhutilPhabricatorAuthAdapter' => 'auth/PhutilPhabricatorAuthAdapter.php',
     'PhutilPhtTestCase' => 'internationalization/__tests__/PhutilPhtTestCase.php',
+    'PhutilPregsprintfTestCase' => 'xsprintf/__tests__/PhutilPregsprintfTestCase.php',
     'PhutilProcessGroupDaemon' => 'daemon/torture/PhutilProcessGroupDaemon.php',
     'PhutilProtocolChannel' => 'channel/PhutilProtocolChannel.php',
     'PhutilProxyException' => 'error/PhutilProxyException.php',
@@ -463,6 +464,7 @@
     'phutil_utf8v_combined' => 'utils/utf8.php',
     'phutil_var_export' => 'utils/utils.php',
     'ppull' => 'utils/utils.php',
+    'pregsprintf' => 'xsprintf/pregsprintf.php',
     'qsprintf' => 'xsprintf/qsprintf.php',
     'qsprintf_check_scalar_type' => 'xsprintf/qsprintf.php',
     'qsprintf_check_type' => 'xsprintf/qsprintf.php',
@@ -484,6 +486,7 @@
     'xsprintf_ldap' => 'xsprintf/ldapsprintf.php',
     'xsprintf_mercurial' => 'xsprintf/hgsprintf.php',
     'xsprintf_query' => 'xsprintf/qsprintf.php',
+    'xsprintf_regex' => 'xsprintf/pregsprintf.php',
     'xsprintf_terminal' => 'xsprintf/tsprintf.php',
     'xsprintf_uri' => 'xsprintf/urisprintf.php',
   ),
@@ -764,6 +767,7 @@
     'PhutilPersonaAuthAdapter' => 'PhutilAuthAdapter',
     'PhutilPhabricatorAuthAdapter' => 'PhutilOAuthAuthAdapter',
     'PhutilPhtTestCase' => 'PhutilTestCase',
+    'PhutilPregsprintfTestCase' => 'PhutilTestCase',
     'PhutilProcessGroupDaemon' => 'PhutilTortureTestDaemon',
     'PhutilProtocolChannel' => 'PhutilChannelChannel',
     'PhutilProxyException' => 'Exception',
diff --git a/src/xsprintf/__tests__/PhutilPregsprintfTestCase.php b/src/xsprintf/__tests__/PhutilPregsprintfTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/xsprintf/__tests__/PhutilPregsprintfTestCase.php
@@ -0,0 +1,23 @@
+<?php
+
+final class PhutilPregsprintfTestCase extends PhutilTestCase {
+
+  public function testPregsprintf() {
+    $this->assertEqual(
+      chr(7).'foobar'.chr(7),
+      pregsprintf('%s', '', 'foobar'));
+
+    $this->assertEqual(
+      chr(7).'\.\*\[a\-z\]'.chr(7),
+      pregsprintf('%s', '', '.*[a-z]'));
+
+    $this->assertEqual(
+      chr(7).'.*[a-z]'.chr(7),
+      pregsprintf('%R', '', '.*[a-z]'));
+
+    $this->assertEqual(
+      chr(7).'^abc\.\*xyz.*$'.chr(7).'siU',
+      pregsprintf('^abc%sxyz%R$', 'siU', '.*', '.*'));
+  }
+
+}
diff --git a/src/xsprintf/pregsprintf.php b/src/xsprintf/pregsprintf.php
new file mode 100644
--- /dev/null
+++ b/src/xsprintf/pregsprintf.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Format a regular expression. Supports the following conversions:
+ *
+ *  %s String
+ *    Escapes a string using `preg_quote`.
+ *
+ *  %R Raw
+ *    Inserts a raw regular expression.
+ *
+ * @param  string  sprintf()-style format string.
+ * @param  string  Flags to use with the regular expression.
+ * @param  ...     Zero or more arguments.
+ * @return string  Formatted string.
+ */
+function pregsprintf($pattern /* , ... */) {
+  $args  = func_get_args();
+  $flags = head(array_splice($args, 1, 1));
+
+  $delim    = chr(7);
+  $userdata = array('delimiter' => $delim);
+
+  $pattern = xsprintf('xsprintf_regex', $userdata, $args);
+  return $delim.$pattern.$delim.$flags;
+}
+
+/**
+ * @{function:xsprintf} callback for regular expressions.
+ */
+function xsprintf_regex($userdata, &$pattern, &$pos, &$value, &$length) {
+  $delim = idx($userdata, 'delimiter');
+  $type  = $pattern[$pos];
+
+  switch ($type) {
+    case 's':
+      $value = preg_quote($value, $delim);
+      break;
+    case 'R':
+      $type = 's';
+      break;
+  }
+
+  $pattern[$pos] = $type;
+}