7.x common.inc drupal_html_id($id)

Prepares a string for use as a valid HTML ID and guarantees uniqueness.

This function ensures that each passed HTML ID value only exists once on the page. By tracking the already returned ids, this function enables forms, blocks, and other content to be output multiple times on the same page, without breaking (X)HTML validation.

For already existing IDs, a counter is appended to the ID string. Therefore, JavaScript and CSS code should not rely on any value that was generated by this function and instead should rely on manually added CSS classes or similarly reliable constructs.

Two consecutive hyphens separate the counter from the original ID. To manage uniqueness across multiple Ajax requests on the same page, Ajax requests POST an array of all IDs currently present on the page, which are used to prime this function's cache upon first invocation.

To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive hyphens in the originally passed $id are replaced with a single hyphen.


$id: The ID to clean.

Return value

The cleaned ID.

12 calls to drupal_html_id()
comment_form in modules/comment/comment.module
Generate the basic commenting form, for appending to a node or display on a separate page.
DrupalHTMLIdentifierTestCase::testDrupalHTMLId in modules/simpletest/tests/common.test
Tests that drupal_html_id() cleans the ID properly.
drupal_prepare_form in includes/form.inc
Prepares a structured form array.
drupal_pre_render_link in includes/common.inc
#pre_render callback to render a link into #markup.
field_multiple_value_form in modules/field/field.form.inc
Special handling to create form elements for multiple values.

... See full list

2 string references to 'drupal_html_id'
DrupalHTMLIdentifierTestCase::testDrupalHTMLId in modules/simpletest/tests/common.test
Tests that drupal_html_id() cleans the ID properly.
drupal_process_form in includes/form.inc
Processes a form submission.


includes/common.inc, line 3993
Common functions that many Drupal modules will need to reference.


function drupal_html_id($id) {

  // If this is an Ajax request, then content returned by this page request will
  // be merged with content already on the base page. The HTML IDs must be
  // unique for the fully merged content. Therefore, initialize $seen_ids to
  // take into account IDs that are already in use on the base page.
  static $drupal_static_fast;
  if (!isset($drupal_static_fast['seen_ids_init'])) {
    $drupal_static_fast['seen_ids_init'] =& drupal_static(__FUNCTION__ . ':init');
  $seen_ids_init =& $drupal_static_fast['seen_ids_init'];
  if (!isset($seen_ids_init)) {

    // Ideally, Drupal would provide an API to persist state information about
    // prior page requests in the database, and we'd be able to add this
    // function's $seen_ids static variable to that state information in order
    // to have it properly initialized for this page request. However, no such
    // page state API exists, so instead, ajax.js adds all of the in-use HTML
    // IDs to the POST data of Ajax submissions. Direct use of $_POST is
    // normally not recommended as it could open up security risks, but because
    // the raw POST data is cast to a number before being returned by this
    // function, this usage is safe.
    if (empty($_POST['ajax_html_ids'])) {
      $seen_ids_init = array();
    else {

      // This function ensures uniqueness by appending a counter to the base id
      // requested by the calling function after the first occurrence of that
      // requested id. $_POST['ajax_html_ids'] contains the ids as they were
      // returned by this function, potentially with the appended counter, so
      // we parse that to reconstruct the $seen_ids array.
      if (isset($_POST['ajax_html_ids'][0]) && strpos($_POST['ajax_html_ids'][0], ',') === FALSE) {
        $ajax_html_ids = $_POST['ajax_html_ids'];
      else {

        // jquery.form.js may send the server a comma-separated string as the
        // first element of an array (see http://drupal.org/node/1575060), so
        // we need to convert it to an array in that case.
        $ajax_html_ids = explode(',', $_POST['ajax_html_ids'][0]);
      foreach ($ajax_html_ids as $seen_id) {

        // We rely on '--' being used solely for separating a base id from the
        // counter, which this function ensures when returning an id.
        $parts = explode('--', $seen_id, 2);
        if (!empty($parts[1]) && is_numeric($parts[1])) {
          list($seen_id, $i) = $parts;
        else {
          $i = 1;
        if (!isset($seen_ids_init[$seen_id]) || $i > $seen_ids_init[$seen_id]) {
          $seen_ids_init[$seen_id] = $i;
  if (!isset($drupal_static_fast['seen_ids'])) {
    $drupal_static_fast['seen_ids'] =& drupal_static(__FUNCTION__, $seen_ids_init);
  $seen_ids =& $drupal_static_fast['seen_ids'];
  $id = strtr(drupal_strtolower($id), array(
    ' ' => '-',
    '_' => '-',
    '[' => '-',
    ']' => '',

  // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can
  // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
  // colons (":"), and periods ("."). We strip out any character not in that
  // list. Note that the CSS spec doesn't allow colons or periods in identifiers
  // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two
  // characters as well.
  $id = preg_replace('/[^A-Za-z0-9\\-_]/', '', $id);

  // Removing multiple consecutive hyphens.
  $id = preg_replace('/\\-+/', '-', $id);

  // Ensure IDs are unique by appending a counter after the first occurrence.
  // The counter needs to be appended with a delimiter that does not exist in
  // the base ID. Requiring a unique delimiter helps ensure that we really do
  // return unique IDs and also helps us re-create the $seen_ids array during
  // Ajax requests.
  if (isset($seen_ids[$id])) {
    $id = $id . '--' . ++$seen_ids[$id];
  else {
    $seen_ids[$id] = 1;
  return $id;


matslats’s picture

As Ryan Szrama says:

if you need to do full form replacement yourself, be sure not to target the form using its id. Each time it is rebuilt, the HTML id is incremented by drupal_html_id(), so the replacement will only work the first time. Instead, either target a container within the form or some other selector that won't change each refresh.

chrisjlee’s picture

jrb’s picture

Why does this function replace underscores with dashes? The w3.org comment says that underscores are OK, and the first call to preg_replace() explicitly allows them. Is there a reason?

seutje’s picture

To be fair, it's quoting a spec that was last updated in 1999 and has been superseded since. Meanwhile, half the codebase isn't conform that 1999 spec anymore, because html5 and stuff! Consistency++

jrb’s picture

It appears to have originally come from this issue: https://www.drupal.org/node/464862

"We currently don't use underscores in class names, so this new function should replace them with dashes by default, but allow callers to relax that restriction if they have a use case (like integrating with 3rd party code.)"

We seem to have lost the ability to "relax" this.

davidwbarratt’s picture

This function does not seem to prevent an ID from starting with a numeric digit (or any other non latter character): http://stackoverflow.com/a/7987693

Is there a work around to this problem?


seutje’s picture

The solution is simple: don't care, browsers don't care either.

hayk.hayrapetyan’s picture

Hi all,
Maybe my question has very simple answer, but I can't find the solution 5 days.
I write the module, with form. The form has drop-down menu. the drop-down menu items I select from mysql db courses table course array. In mysql I have this tables:
id, course, description, amount.
My problem is very simple, I want to select from db course description, after selecting the course from drop-down and present before the drop-down menu.

Sorry, Thx

jonhattan’s picture