diff --git a/Bugzilla.pm b/Bugzilla.pm
index 86d6e6e70de5d9d0e665839c2fb8f25bfb6b9bc2..5267f3dbd6ee4aacd284a346dc62b1904d433207 100644
--- a/Bugzilla.pm
+++ b/Bugzilla.pm
@@ -35,6 +35,7 @@ use Bugzilla::Template;
 use Bugzilla::User;
 use Bugzilla::Error;
 use Bugzilla::Util;
+use Bugzilla::Field;
 
 use File::Basename;
 
@@ -276,6 +277,17 @@ sub switch_to_main_db {
     return $class->dbh;
 }
 
+sub get_fields {
+    my $class = shift;
+    my $criteria = shift;
+    return Bugzilla::Field::match($criteria);
+}
+
+sub custom_field_names {
+    # Get a list of custom fields and convert it into a list of their names.
+    return map($_->{name}, Bugzilla::Field::match({ custom=>1, obsolete=>0 }));
+}
+
 # Private methods
 
 # Per process cleanup
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index 61798f7cb017ad8d4e6484d2b56c5bd45dc5142b..cfa5f49f659cf35331815a56f30a5014182a9869 100755
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -90,6 +90,8 @@ sub fields {
         push @fields, qw(estimated_time remaining_time actual_time deadline);
     }
 
+    push(@fields, Bugzilla->custom_field_names);
+
     return @fields;
 }
 
@@ -162,6 +164,11 @@ sub initBug  {
 
   $self->{'who'} = new Bugzilla::User($user_id);
 
+    my $custom_fields = "";
+    if (length(Bugzilla->custom_field_names) > 0) {
+        $custom_fields = ", " . join(", ", Bugzilla->custom_field_names);
+    }
+
   my $query = "
     SELECT
       bugs.bug_id, alias, products.classification_id, classifications.name,
@@ -175,7 +182,8 @@ sub initBug  {
       delta_ts, COALESCE(SUM(votes.vote_count), 0),
       reporter_accessible, cclist_accessible,
       estimated_time, remaining_time, " .
-      $dbh->sql_date_format('deadline', '%Y-%m-%d') . "
+      $dbh->sql_date_format('deadline', '%Y-%m-%d') .
+      $custom_fields . "
     FROM bugs
        LEFT JOIN votes
               ON bugs.bug_id = votes.bug_id
@@ -212,7 +220,8 @@ sub initBug  {
                        "target_milestone", "qa_contact_id", "status_whiteboard",
                        "creation_ts", "delta_ts", "votes",
                        "reporter_accessible", "cclist_accessible",
-                       "estimated_time", "remaining_time", "deadline")
+                       "estimated_time", "remaining_time", "deadline",
+                       Bugzilla->custom_field_names)
       {
         $fields{$field} = shift @row;
         if (defined $fields{$field}) {
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index 09717486ee6bd6f720885456035a13f08727cc75..6ff7d8fa986dc8b53fb7d8a7c0c9271c40c8b328 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -91,6 +91,9 @@ use base qw(Exporter);
     ADMIN_GROUP_NAME
 
     SENDMAIL_EXE
+
+    FIELD_TYPE_UNKNOWN
+    FIELD_TYPE_FREETEXT
 );
 
 @Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
@@ -243,4 +246,14 @@ use constant ADMIN_GROUP_NAME => 'admin';
 # Path to sendmail.exe (Windows only)
 use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
 
+# Field types.  Match values in fielddefs.type column.  These are purposely
+# not named after database column types, since Bugzilla fields comprise not
+# only storage but also logic.  For example, we might add a "user" field type
+# whose values are stored in an integer column in the database but for which
+# we do more than we would do for a standard integer type (f.e. we might
+# display a user picker).
+
+use constant FIELD_TYPE_UNKNOWN   => 0;
+use constant FIELD_TYPE_FREETEXT  => 1;
+
 1;
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index 63b19578d045c27414d2225784d023a14c880571..3caeba7074741b9ca2cffa0b855bbaab7560024f 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -36,6 +36,7 @@ package Bugzilla::DB::Schema;
 use strict;
 use Bugzilla::Error;
 use Bugzilla::Util;
+use Bugzilla::Constants;
 
 use Safe;
 # Historical, needed for SCHEMA_VERSION = '1.00'
@@ -453,6 +454,10 @@ use constant ABSTRACT_SCHEMA => {
             fieldid     => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1,
                             PRIMARYKEY => 1},
             name        => {TYPE => 'varchar(64)', NOTNULL => 1},
+            type        => {TYPE => 'INT2', NOTNULL => 1,
+                            DEFAULT => FIELD_TYPE_UNKNOWN},
+            custom      => {TYPE => 'BOOLEAN', NOTNULL => 1,
+                            DEFAULT => 'FALSE'},
             description => {TYPE => 'MEDIUMTEXT', NOTNULL => 1},
             mailhead    => {TYPE => 'BOOLEAN', NOTNULL => 1,
                             DEFAULT => 'FALSE'},
diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm
index 09c4731ac8448eb5ff834bb0c7b2d5ceab4e5dfe..8585ff760fd76e9bbef4442d48432103a20916ad 100644
--- a/Bugzilla/Field.pm
+++ b/Bugzilla/Field.pm
@@ -14,6 +14,57 @@
 #
 # Contributor(s): Dan Mosedale <dmose@mozilla.org>
 #                 Frédéric Buclin <LpSolit@gmail.com>
+#                 Myk Melez <myk@mozilla.org>
+
+=head1 NAME
+
+Bugzilla::Field - a particular piece of information about bugs
+                  and useful routines for form field manipulation
+
+=head1 SYNOPSIS
+
+  use Bugzilla;
+  use Data::Dumper;
+
+  # Display information about all fields.
+  print Dumper(Bugzilla->get_fields());
+  
+  # Display information about non-obsolete custom fields.
+  print Dumper(Bugzilla->get_fields({ obsolete => 1, custom => 1 }));
+
+  # Display a list of the names of non-obsolete custom fields.
+  print Bugzilla->custom_field_names;
+
+  use Bugzilla::Field;
+
+  # Display information about non-obsolete custom fields.
+  # Bugzilla->get_fields() is a wrapper around Bugzilla::Field::match(),
+  # so both methods take the same arguments.
+  print Dumper(Bugzilla::Field::match({ obsolete => 1, custom => 1 }));
+  
+  # Create a custom field.
+  my $field = Bugzilla::Field::create("hilarity", "Hilarity");
+  print "$field->{description} is a custom field\n";
+  
+  # Instantiate a Field object for an existing field.
+  my $field = new Bugzilla::Field('qacontact_accessible');
+  if ($field->{obsolete}) {
+      print "$field->{description} is obsolete\n";
+  }
+
+  # Validation Routines
+  check_form_field($cgi, $fieldname, \@legal_values);
+  check_form_field_defined($cgi, $fieldname);
+  $fieldid = get_field_id($fieldname);
+
+=head1 DESCRIPTION
+
+Field.pm defines field objects, which represent the particular pieces
+of information that Bugzilla stores about bugs.
+
+This package also provides functions for dealing with CGI form fields.
+
+=cut
 
 package Bugzilla::Field;
 
@@ -24,73 +75,214 @@ use base qw(Exporter);
                               get_field_id);
 
 use Bugzilla::Util;
+use Bugzilla::Constants;
 use Bugzilla::Error;
 
+use constant DB_COLUMNS => (
+    'fieldid AS id',
+    'name',
+    'description',
+    'type',
+    'custom',
+    'obsolete'
+);
+
+our $columns = join(", ", DB_COLUMNS);
+
+sub new {
+    my $invocant = shift;
+    my $name = shift;
+    my $self = shift || Bugzilla->dbh->selectrow_hashref(
+                            "SELECT $columns FROM fielddefs WHERE name = ?",
+                            undef,
+                            $name
+                        );
+    bless($self, $invocant);
+    return $self;
+}
 
-sub check_form_field {
-    my ($cgi, $fieldname, $legalsRef) = @_;
-    my $dbh = Bugzilla->dbh;
+=pod
 
-    if (!defined $cgi->param($fieldname)
-        || trim($cgi->param($fieldname)) eq ""
-        || (defined($legalsRef)
-            && lsearch($legalsRef, $cgi->param($fieldname)) < 0))
-    {
-        trick_taint($fieldname);
-        my ($result) = $dbh->selectrow_array("SELECT description FROM fielddefs
-                                              WHERE name = ?", undef, $fieldname);
-        
-        my $field = $result || $fieldname;
-        ThrowCodeError("illegal_field", { field => $field });
-    }
-}
+=head2 Instance Properties
 
-sub check_form_field_defined {
-    my ($cgi, $fieldname) = @_;
+=over
 
-    if (!defined $cgi->param($fieldname)) {
-        ThrowCodeError("undefined_field", { field => $fieldname });
-    }
-}
+=item C<id>
+
+the unique identifier for the field;
+
+=back
+
+=cut
+
+sub id { return $_[0]->{id} }
+
+=over
+
+=item C<name>
+
+the name of the field in the database; begins with "cf_" if field
+is a custom field, but test the value of the boolean "custom" property
+to determine if a given field is a custom field;
+
+=back
+
+=cut
+
+sub name { return $_[0]->{name} }
+
+=over
+
+=item C<description>
+
+a short string describing the field; displayed to Bugzilla users
+in several places within Bugzilla's UI, f.e. as the form field label
+on the "show bug" page;
+
+=back
+
+=cut
+
+sub description { return $_[0]->{description} }
+
+=over
+
+=item C<type>
+
+an integer specifying the kind of field this is; values correspond to
+the FIELD_TYPE_* constants in Constants.pm
+
+=back
+
+=cut
+
+sub type { return $_[0]->{type} }
+
+=over
+
+=item C<custom>
+
+a boolean specifying whether or not the field is a custom field;
+if true, field name should start "cf_", but use this property to determine
+which fields are custom fields;
+
+=back
+
+=cut
+
+sub custom { return $_[0]->{custom} }
+
+=over
+
+=item C<obsolete>
+
+a boolean specifying whether or not the field is obsolete;
+
+=back
+
+=cut
+
+sub obsolete { return $_[0]->{obsolete} }
+
+
+=pod
+
+=head2 Class Methods
+
+=over
+
+=item C<create($name, $desc)>
+
+Description: creates a new custom field.
+
+Params:      C<$name> - string - the name of the field;
+             C<$desc> - string - the field label to display in the UI.
+
+Returns:     a field object.
+
+=back
+
+=cut
+
+sub create {
+    my ($name, $desc, $custom) = @_;
+    
+    # Convert the $custom argument into a DB-compatible value.
+    $custom = $custom ? 1 : 0;
 
-sub get_field_id {
-    my ($name) = @_;
     my $dbh = Bugzilla->dbh;
 
-    trick_taint($name);
-    my $id = $dbh->selectrow_array('SELECT fieldid FROM fielddefs
-                                    WHERE name = ?', undef, $name);
+    # Some day we'll allow invocants to specify the sort key.
+    my ($sortkey) =
+      $dbh->selectrow_array("SELECT MAX(sortkey) + 1 FROM fielddefs");
 
-    ThrowCodeError('invalid_field_name', {field => $name}) unless $id;
-    return $id
+    # Some day we'll require invocants to specify the field type.
+    my $type = FIELD_TYPE_FREETEXT;
+
+    # Create the database column that stores the data for this field.
+    $dbh->bz_add_column("bugs", $name, { TYPE => 'varchar(255)' });
+
+    # Add the field to the list of fields at this Bugzilla installation.
+    my $sth = $dbh->prepare(
+                  "INSERT INTO fielddefs (name, description, sortkey, type,
+                                          custom, mailhead)
+                   VALUES (?, ?, ?, ?, ?, 1)"
+              );
+    $sth->execute($name, $desc, $sortkey, $type, $custom);
+
+    return new Bugzilla::Field($name);
 }
 
-1;
 
-__END__
+=pod
 
-=head1 NAME
+=over
 
-Bugzilla::Field - Useful routines for fields manipulation
+=item C<match($criteria)>
 
-=head1 SYNOPSIS
+Description: returns a list of fields that match the specified criteria.
 
-  use Bugzilla::Field;
+Params:    C<$criteria> - hash reference - the criteria to match against.
+           Hash keys represent field properties; hash values represent
+           their values.  All criteria are optional.  Valid criteria are
+           "custom" and "obsolete", and both take boolean values.
 
-  # Validation Routines
-  check_form_field($cgi, $fieldname, \@legal_values);
-  check_form_field_defined($cgi, $fieldname);
-  $fieldid = get_field_id($fieldname);
+           Note: Bugzilla->get_fields() and Bugzilla->custom_field_names
+           wrap this method for most callers.
 
-=head1 DESCRIPTION
+Returns:   a list of field objects.
 
-This package provides functions for dealing with CGI form fields.
+=back
 
-=head1 FUNCTIONS
+=cut
 
-This package provides several types of routines:
+sub match {
+    my ($criteria) = @_;
+  
+    my @terms;
+    if (defined $criteria->{name}) {
+        push(@terms, "name=" . Bugzilla->dbh->quote($criteria->{name}));
+    }
+    if (defined $criteria->{custom}) {
+        push(@terms, "custom=" . ($criteria->{custom} ? "1" : "0"));
+    }
+    if (defined $criteria->{obsolete}) {
+        push(@terms, "obsolete=" . ($criteria->{obsolete} ? "1" : "0"));
+    }
+    my $where = (scalar(@terms) > 0) ? "WHERE " . join(" AND ", @terms) : "";
+  
+    my $records = Bugzilla->dbh->selectall_arrayref(
+                      "SELECT $columns FROM fielddefs $where ORDER BY sortkey",
+                      { Slice => {}}
+                  );
+    # Generate a array of field objects from the array of field records.
+    my @fields = map( new Bugzilla::Field(undef, $_), @$records );
+    return @fields;
+}
+
+=pod
 
-=head2 Validation
+=head2 Data Validation
 
 =over
 
@@ -108,6 +300,32 @@ Params:      $cgi          - a CGI object
 
 Returns:     nothing
 
+=back
+
+=cut
+
+sub check_form_field {
+    my ($cgi, $fieldname, $legalsRef) = @_;
+    my $dbh = Bugzilla->dbh;
+
+    if (!defined $cgi->param($fieldname)
+        || trim($cgi->param($fieldname)) eq ""
+        || (defined($legalsRef)
+            && lsearch($legalsRef, $cgi->param($fieldname)) < 0))
+    {
+        trick_taint($fieldname);
+        my ($result) = $dbh->selectrow_array("SELECT description FROM fielddefs
+                                              WHERE name = ?", undef, $fieldname);
+        
+        my $field = $result || $fieldname;
+        ThrowCodeError("illegal_field", { field => $field });
+    }
+}
+
+=pod
+
+=over
+
 =item C<check_form_field_defined($cgi, $fieldname)>
 
 Description: Makes sure the field $fieldname is defined and its value
@@ -118,14 +336,48 @@ Params:      $cgi       - a CGI object
 
 Returns:     nothing
 
+=back
+
+=cut
+
+sub check_form_field_defined {
+    my ($cgi, $fieldname) = @_;
+
+    if (!defined $cgi->param($fieldname)) {
+        ThrowCodeError("undefined_field", { field => $fieldname });
+    }
+}
+
+=pod
+
+=over
+
 =item C<get_field_id($fieldname)>
 
 Description: Returns the ID of the specified field name and throws
              an error if this field does not exist.
 
-Params:      $fieldname - a field name
+Params:      $name - a field name
 
 Returns:     the corresponding field ID or an error if the field name
              does not exist.
 
 =back
+
+=cut
+
+sub get_field_id {
+    my ($name) = @_;
+    my $dbh = Bugzilla->dbh;
+
+    trick_taint($name);
+    my $id = $dbh->selectrow_array('SELECT fieldid FROM fielddefs
+                                    WHERE name = ?', undef, $name);
+
+    ThrowCodeError('invalid_field_name', {field => $name}) unless $id;
+    return $id
+}
+
+1;
+
+__END__
diff --git a/checksetup.pl b/checksetup.pl
index e8528aee1bb9010a8571af0154d30692acc05710..fdb678e21affe4d850ca25d8de063c69d95dd86a 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -1305,7 +1305,7 @@ unless ($switch{'no_templates'}) {
 # These are the files which need to be marked executable
 my @executable_files = ('whineatnews.pl', 'collectstats.pl',
    'checksetup.pl', 'importxml.pl', 'runtests.pl', 'testserver.pl',
-   'whine.pl');
+   'whine.pl', 'customfield.pl');
 
 # tell me if a file is executable.  All CGI files and those in @executable_files
 # are executable
@@ -4240,6 +4240,13 @@ $dbh->bz_alter_column('logincookies', 'cookie',
                       {TYPE => 'varchar(16)', PRIMARYKEY => 1, NOTNULL => 1});
 
 
+# 2005-08-10 Myk Melez <myk@mozilla.org> bug 287325
+# Record each field's type and whether or not it's a custom field in fielddefs.
+$dbh->bz_add_column('fielddefs', 'type',
+                    { TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0 });
+$dbh->bz_add_column('fielddefs', 'custom',
+                    { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' });
+
 # If you had to change the --TABLE-- definition in any way, then add your
 # differential change code *** A B O V E *** this comment.
 #
diff --git a/customfield.pl b/customfield.pl
new file mode 100755
index 0000000000000000000000000000000000000000..106c60582bd127bd8be2354253c9dd3a713d463a
--- /dev/null
+++ b/customfield.pl
@@ -0,0 +1,88 @@
+#!/usr/bin/perl -wT
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (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.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Myk Melez <myk@mozilla.org>
+
+################################################################################
+# Script Initialization
+################################################################################
+
+use strict;
+
+use lib ".";
+require "globals.pl";
+
+use Bugzilla::Field;
+use Getopt::Long;
+
+my ($name, $desc);
+my $result = GetOptions("name=s"             => \$name,
+                        "description|desc=s" => \$desc);
+
+if (!$name or !$desc) {
+    my $command =
+      $^O =~ /MSWin32/i ? "perl -T customfield.pl" : "./customfield.pl";
+    print <<END;
+Usage:
+
+  Use this script to add a custom field to your Bugzilla installation
+  by invoking it with the --name and --desc command-line options:
+
+  $command --name=<field_name> --desc="<field description>"
+
+  <field_name> is the name of the custom field in the database.
+  The string "cf_" will be prepended to this name to distinguish
+  the field from standard fields.  This name must conform to the
+  naming rules for the database server you use.
+  
+  <field description> is a short string describing the field.  It will
+  be displayed to Bugzilla users in several parts of Bugzilla's UI,
+  for example as the label for the field on the "show bug" page.
+
+Warning:
+
+  Custom fields can make Bugzilla less usable.  See this URL
+  for alternatives to custom fields:
+  
+  http://www.gerv.net/hacking/custom-fields.html
+  
+  You should try to implement applicable alternatives before using
+  this script to add a custom field.
+END
+
+    exit;
+}
+
+# Prepend cf_ to the custom field name to distinguish it from standard fields.
+$name =~ /^cf_/
+  or $name = "cf_" . $name;
+
+# Exit gracefully if there is already a field with the given name.
+if (scalar(Bugzilla::Field::match({ name=>$name })) > 0) {
+    print "There is already a field named $name.  Please choose " .
+          "a different name.\n";
+    exit;
+}
+
+
+# Create the field.
+print "Creating custom field $name ...\n";
+my $field = Bugzilla::Field::create($name, $desc, 1);
+print "Custom field $name created.\n";
diff --git a/editcomponents.cgi b/editcomponents.cgi
index c65fd3167232076b7adcf701592dd16f2273b18d..3cbd71a9c50776a706fb44db83c085b722900c7a 100755
--- a/editcomponents.cgi
+++ b/editcomponents.cgi
@@ -31,6 +31,7 @@ use lib ".";
 
 require "globals.pl";
 
+use Bugzilla;
 use Bugzilla::Constants;
 use Bugzilla::Config qw(:DEFAULT $datadir);
 use Bugzilla::Series;
diff --git a/editmilestones.cgi b/editmilestones.cgi
index c87828526576bfe362dcf22a99c815fed06a9d27..bb80164ab92df08a40a29c9415264df85177b701 100755
--- a/editmilestones.cgi
+++ b/editmilestones.cgi
@@ -21,6 +21,7 @@ use lib ".";
 
 require "globals.pl";
 
+use Bugzilla;
 use Bugzilla::Constants;
 use Bugzilla::Config qw(:DEFAULT $datadir);
 use Bugzilla::Product;
diff --git a/editproducts.cgi b/editproducts.cgi
index 2b7c5dc5d5b204b9571336bdefcb7bc54d68c4bf..d9ebcedd9ddf517748a74e8c4c7fc71c3c232cd5 100755
--- a/editproducts.cgi
+++ b/editproducts.cgi
@@ -33,6 +33,7 @@
 
 use strict;
 use lib ".";
+use Bugzilla;
 use Bugzilla::Constants;
 require "globals.pl";
 use Bugzilla::Bug;
diff --git a/process_bug.cgi b/process_bug.cgi
index 77496f2a38952b5ef0dab3354b316becc4d49e64..fcd6408d39d8f8802a53a0472fbb992ebf418ff4 100755
--- a/process_bug.cgi
+++ b/process_bug.cgi
@@ -855,6 +855,18 @@ foreach my $field ("rep_platform", "priority", "bug_severity",
     }
 }
 
+# Add custom fields data to the query that will update the database.
+foreach my $field (Bugzilla->custom_field_names) {
+    if (defined $cgi->param($field)
+        && (!$cgi->param('dontchange')
+            || $cgi->param($field) ne $cgi->param('dontchange')))
+    {
+        DoComma();
+        $::query .= "$field = " . SqlQuote(trim($cgi->param($field)));
+    }
+}
+
+
 my $prod_id;
 my $prod_changed;
 my @newprod_ids;
diff --git a/show_activity.cgi b/show_activity.cgi
index 9b7273a4cd35c133c818d2f1b354b83a2f4ccf92..e44bc705431e061730e74026df26f8408ea09c0b 100755
--- a/show_activity.cgi
+++ b/show_activity.cgi
@@ -28,6 +28,7 @@ use lib qw(.);
 
 require "globals.pl";
 
+use Bugzilla;
 use Bugzilla::Bug;
 
 my $cgi = Bugzilla->cgi;
diff --git a/showdependencytree.cgi b/showdependencytree.cgi
index d9d71b0abc43ebeb1faf5a808c097aa096bd6efc..03abb272993b3fba6401ba03719ff169d60fc04a 100755
--- a/showdependencytree.cgi
+++ b/showdependencytree.cgi
@@ -27,6 +27,7 @@ use strict;
 
 use lib qw(.);
 require "globals.pl";
+use Bugzilla;
 use Bugzilla::User;
 use Bugzilla::Bug;
 
diff --git a/summarize_time.cgi b/summarize_time.cgi
index 44c4f8d50d485e95079563baf640c0603370c9a4..0827077e8d2c0b9b25c6e9f88bc4d9a44cb66441 100755
--- a/summarize_time.cgi
+++ b/summarize_time.cgi
@@ -23,6 +23,7 @@ use lib qw(.);
 use Date::Parse;         # strptime
 use Date::Format;        # strftime
 
+use Bugzilla;
 use Bugzilla::Bug;       # EmitDependList
 use Bugzilla::Util;      # trim
 use Bugzilla::Constants; # LOGIN_*
diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl
index 2252528ac39417e5c6d6bb3bbb58edbd1a3f3013..9768dd3b478b1dffb9144cf872aa8483b8c4a57f 100644
--- a/template/en/default/bug/edit.html.tmpl
+++ b/template/en/default/bug/edit.html.tmpl
@@ -497,6 +497,15 @@
     </table>
   [% END %]
 
+[%# *** Custom Fields *** %]
+
+[% USE Bugzilla %]
+<table>
+  [% FOREACH field = Bugzilla.get_fields({ obsolete => 0, custom => 1 }) %]
+    [% PROCESS bug/field.html.tmpl value=bug.${field.name} %]
+  [% END %]
+</table>
+
 [%# *** Attachments *** %]
 
   [% PROCESS attachment/list.html.tmpl
diff --git a/template/en/default/bug/field.html.tmpl b/template/en/default/bug/field.html.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..cbde9cf2783af025cf2a0289bac313021f230e23
--- /dev/null
+++ b/template/en/default/bug/field.html.tmpl
@@ -0,0 +1,36 @@
+[%# 1.0@bugzilla.org %]
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (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.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # The Initial Developer of the Original Code is Netscape Communications
+  # Corporation. Portions created by Netscape are
+  # Copyright (C) 1998 Netscape Communications Corporation. All
+  # Rights Reserved.
+  #
+  # Contributor(s): Myk Melez <myk@mozilla.org>
+  #%]
+
+<tr>
+  [% SWITCH field.type %]
+  [% CASE constants.FIELD_TYPE_FREETEXT %]
+    <th align="right">
+      <label for="[% field.name FILTER html %]">
+        [% field.description FILTER html %]:
+      </label>
+    </th>
+    <td>
+      <input name="[% field.name FILTER html %]"
+             value="[% value FILTER html %]"
+             size="60">
+    </td>
+  [% END %]
+</tr>