diff --git a/djblets/webapi/encoders.py b/djblets/webapi/encoders.py
index be934c93536fe25fa41d2185ecdd4d308902b3ff..8bc2f59cd677e02bd6119898b17f01193bd8b308 100644
--- a/djblets/webapi/encoders.py
+++ b/djblets/webapi/encoders.py
@@ -8,6 +8,9 @@ from django.contrib.auth.models import User, Group
 from django.db.models.query import QuerySet
 from django.utils import six
 from django.utils.six.moves import cStringIO as StringIO
+from pygments import highlight
+from pygments.lexers import JsonLexer
+from pygments.formatters import HtmlFormatter
 
 from djblets.util.serializers import DjbletsJSONEncoder
 
@@ -72,6 +75,7 @@ class BasicAPIEncoder(WebAPIEncoder):
 
 class ResourceAPIEncoder(WebAPIEncoder):
     """An encoder that encodes objects based on registered resources."""
+
     def encode(self, o, *args, **kwargs):
         if isinstance(o, QuerySet):
             return list(o)
@@ -102,6 +106,7 @@ class JSONEncoderAdapter(json.JSONEncoder):
     WebAPIEncoder, but can be used in other projects for more specific
     purposes as well.
     """
+
     def __init__(self, encoder, *args, **kwargs):
         json.JSONEncoder.__init__(self, *args, **kwargs)
         self.encoder = encoder
@@ -130,6 +135,7 @@ class XMLEncoderAdapter(object):
 
     This takes an existing encoder and adapts it to output a simple XML format.
     """
+
     def __init__(self, encoder, *args, **kwargs):
         self.encoder = encoder
 
@@ -209,8 +215,30 @@ class XMLEncoderAdapter(object):
             self.xml.ignorableWhitespace('\n' + ' ' * self.level)
 
 
-_registered_encoders = None
+class HTMLEncoderAdapter(object):
+    """Adapts a WebAPIEncoder to output HTML.
+
+    This takes an existing encoder and adapts it to output in HTML format.
+    """
+
+    def __init__(self, encoder, *args, **kwargs):
+        self.encoder = encoder
 
+    def encode(self, o, *args, **kwargs):
+        """Encode the resource data into a valid response type.
+
+        This takes the resource data and renders it in a template.
+        """
+        self.encode_args = args
+        self.encode_kwargs = kwargs
+        data = json.dumps(o, indent=4, separators=(',', ': '),
+                          cls=DjbletsJSONEncoder)
+        lexer = JsonLexer()
+        result = highlight(data, lexer, HtmlFormatter())
+
+        return result
+
+_registered_encoders = None
 
 def get_registered_encoders():
     """
diff --git a/djblets/webapi/resources/base.py b/djblets/webapi/resources/base.py
index d240a53ffae889d328d20bb66652e5205199ad02..91ad0f241cd2d8b389eb6a46dabc6bb19310d8b5 100644
--- a/djblets/webapi/resources/base.py
+++ b/djblets/webapi/resources/base.py
@@ -16,13 +16,15 @@ from django.db.models.fields.related import (
 from django.db.models.query import QuerySet
 from django.http import (HttpResponseNotAllowed, HttpResponse,
                          HttpResponseNotModified)
+from django.template.loader import render_to_string
 from django.utils import six
 from django.views.decorators.vary import vary_on_headers
 
-from djblets.util.http import (get_modified_since, encode_etag,
-                               etag_if_none_match,
+from djblets.util.http import (encode_etag, etag_if_none_match,
+                               get_modified_since, get_http_requested_mimetype,
+                               is_mimetype_a,
                                set_last_modified, set_etag,
-                               get_http_requested_mimetype)
+                               )
 from djblets.urls.patterns import never_cache_patterns
 from djblets.webapi.auth.backends import check_login
 from djblets.webapi.resources.registry import (get_resource_for_object,
@@ -41,7 +43,6 @@ from djblets.webapi.errors import (DOES_NOT_EXIST,
                                    PERMISSION_DENIED,
                                    WebAPIError)
 
-
 class WebAPIResource(object):
     """A resource handling HTTP operations for part of the API.
 
@@ -191,11 +192,97 @@ class WebAPIResource(object):
         else:
             view = None
 
+        response = self.get_response(request, method, view, api_format, *args,
+                                     **kwargs)
+
+        return response
+
+    def call_method_view(self, request, method, view, *args, **kwargs):
+        """Calls the given method view.
+
+        This will just call the given view by default, passing in all
+        args and kwargs.
+
+        This can be overridden by subclasses to perform additional
+        checks or pass additional data to the view.
+        """
+        return view(request, *args, **kwargs)
+
+    def get_response(self, request, method, view, api_format, *args, **kwargs):
+        """Retrieve the appropriate HTTP Handler.
+
+        This will determine the appropriate response type
+        """
         if view and six.callable(view):
             result = self.call_method_view(
                 request, method, view, api_format=api_format, *args, **kwargs)
 
             if isinstance(result, WebAPIResponse):
+
+                payload_only = False
+
+                if method in self.allowed_methods:
+                    if 'payload_only' in request.GET:
+                        payload_only = True
+                    elif 'payload_only' in request.POST:
+                        payload_only = True
+
+                if is_mimetype_a(result.mimetype, 'text/html') or payload_only:
+
+                    resource_fields = None
+                    api_explorer_frontend = 'webapi/payload-only.html'
+
+                    if not payload_only:
+                        api_explorer_frontend = 'webapi/apiexplorer.html'
+
+                        resource_fields = {
+                            'get': {'required': {}, 'optional': {}}}
+
+                        if hasattr(self.get_list, 'required_fields'):
+                            resource_fields['get']['required'] = \
+                                self.build_fields(
+                                self.get_list.required_fields)
+
+                        if hasattr(self.get_list, 'optional_fields'):
+                            resource_fields['get']['optional'] = \
+                                self.build_fields(
+                                self.get_list.optional_fields)
+
+                        if (hasattr(self.create, 'required_fields') or
+                                hasattr(self.create, 'optional_fields')):
+
+                            resource_fields.update(
+                                {
+                                    'get': resource_fields['get'],
+                                    'post': {
+                                        'required': {},
+                                        'optional': {},
+                                    }
+                                })
+
+                            resource_fields['post']['required'] = \
+                                self.build_fields(
+                                self.create.required_fields)
+
+                            resource_fields['post']['optional'] = \
+                                self.build_fields(
+                                self.create.optional_fields)
+
+                    template = render_to_string(
+                        api_explorer_frontend,
+                        {
+                            'resource': self,
+                            'request': request,
+                            'payload': result.content,
+                            'resource_fields': resource_fields,
+                        })
+
+                    # Ensures that HTML response headers are set properly
+                    result['Content-Type'] = 'text/html'
+                    result['Content-Encoding'] = 'text/html'
+                    result['Item-Content-Type'] = 'text/html'
+                    result.content = template
+
                 return result
             elif isinstance(result, WebAPIError):
                 return WebAPIResponseError(
@@ -249,22 +336,53 @@ class WebAPIResource(object):
                         }, **kwargs),
                         **response_args)
             elif isinstance(result, HttpResponse):
+
+                # Future work to be done for API Explorer
+                if self.mime is 'text/html':
+
+                    headers = {}
+
+                    response_args = self.build_response_args(request)
+                    headers.update(response_args.pop('headers', {}))
+
+                    result = WebAPIResponse(
+                        request,
+                        status=result.status_code,
+                        headers=headers,
+                        api_format=api_format,
+                        encoder_kwargs=dict({
+                            'calling_resource': self,
+                        }, **kwargs),
+                        **response_args)
+
                 return result
             else:
                 raise AssertionError(result)
         else:
             return HttpResponseNotAllowed(self.allowed_methods)
 
-    def call_method_view(self, request, method, view, *args, **kwargs):
-        """Calls the given method view.
+    def build_fields(self, resource_fields):
+        """Return formatted fields for the current Resource."""
+        type_mapping = {
+            int: 'Integer',
+            str: 'String',
+            unicode: 'String',
+            bool: 'Boolean',
+            dict: 'Dictionary',
+            file: 'Uploaded File',
+        }
 
-        This will just call the given view by default, passing in all
-        args and kwargs.
+        for resource_name, resource_field in resource_fields.iteritems():
+            if type(resource_field['type']) is list:
+                resource_field['list'] = resource_field['type']
+                resource_field['type_d'] = 'List'
+            elif type(resource_field['type']) is tuple:
+                resource_field['tuple'] = resource_field['type']
+                resource_field['type_d'] = 'Tuple'
+            elif resource_field['type'] in type_mapping:
+                resource_field['type_d'] = type_mapping[resource_field['type']]
 
-        This can be overridden by subclasses to perform additional
-        checks or pass additional data to the view.
-        """
-        return view(request, *args, **kwargs)
+        return resource_fields
 
     @property
     def __name__(self):
@@ -367,8 +485,8 @@ class WebAPIResource(object):
         mimetype = get_http_requested_mimetype(request, supported_mimetypes)
 
         if (self.mimetype_vendor and
-            mimetype in WebAPIResponse.supported_mimetypes):
-            mimetype = self._build_resource_mimetype(mimetype, is_list)
+           mimetype in WebAPIResponse.supported_mimetypes):
+                    mimetype = self._build_resource_mimetype(mimetype, is_list)
 
         response_args = {
             'supported_mimetypes': supported_mimetypes,
@@ -378,11 +496,11 @@ class WebAPIResource(object):
         if is_list:
             for mimetype_pair in self.allowed_mimetypes:
                 if (mimetype_pair.get('list') == mimetype and
-                    mimetype_pair.get('item')):
-                    response_args['headers'] = {
-                        'Item-Content-Type': mimetype_pair['item'],
-                    }
-                    break
+                   mimetype_pair.get('item')):
+                        response_args['headers'] = {
+                            'Item-Content-Type': mimetype_pair['item'],
+                        }
+                        break
 
         return response_args
 
diff --git a/djblets/webapi/responses.py b/djblets/webapi/responses.py
index 04acbdcc2dce6394b432ba335f3f2b8414967dd7..655b1e9e975cdea242dc10a4a76899e9b6d38a77 100644
--- a/djblets/webapi/responses.py
+++ b/djblets/webapi/responses.py
@@ -6,8 +6,8 @@ from django.utils.encoding import force_unicode
 from djblets.util.http import (get_http_requested_mimetype,
                                get_url_params_except,
                                is_mimetype_a)
-from djblets.webapi.encoders import (JSONEncoderAdapter, WebAPIEncoder,
-                                     XMLEncoderAdapter,
+from djblets.webapi.encoders import (HTMLEncoderAdapter, JSONEncoderAdapter,
+                                     WebAPIEncoder, XMLEncoderAdapter,
                                      get_registered_encoders)
 from djblets.webapi.errors import INVALID_FORM_DATA
 
@@ -17,6 +17,7 @@ class WebAPIResponse(HttpResponse):
     supported_mimetypes = [
         'application/json',
         'application/xml',
+        'text/html',
     ]
 
     def __init__(self, request, obj={}, stat='ok', api_format=None,
@@ -35,10 +36,12 @@ class WebAPIResponse(HttpResponse):
             if not api_format:
                 mimetype = get_http_requested_mimetype(request,
                                                        supported_mimetypes)
-            elif api_format == "json":
+            elif api_format == 'json':
                 mimetype = 'application/json'
-            elif api_format == "xml":
+            elif api_format == 'xml':
                 mimetype = 'application/xml'
+            elif api_format == 'html':
+                mimetype = 'text/html'
 
         if not mimetype:
             self.status_code = 400
@@ -100,8 +103,10 @@ class WebAPIResponse(HttpResponse):
             if (self.mimetype == 'text/plain' or
                 is_mimetype_a(self.mimetype, 'application/json')):
                 adapter = JSONEncoderAdapter(encoder)
-            elif is_mimetype_a(self.mimetype, "application/xml"):
+            elif is_mimetype_a(self.mimetype, 'application/xml'):
                 adapter = XMLEncoderAdapter(encoder)
+            elif is_mimetype_a(self.mimetype, 'text/html'):
+                adapter = HTMLEncoderAdapter(encoder)
             else:
                 assert False
 
