diff --git a/django_forms_as_jsonschema/forms.py b/django_forms_as_jsonschema/forms.py
index 976b257d6a3914fb4ded7fef544962a8ae880029..0355f09f5f10fbca6d9c52bcdf95b4e3361ccb37 100644
--- a/django_forms_as_jsonschema/forms.py
+++ b/django_forms_as_jsonschema/forms.py
@@ -1,6 +1,11 @@
 from django.forms import Form
 
+from django_forms_as_jsonschema.schema import Schema
+
 
 class JSONSchemaFormMixin:
     def as_jsonschema(self: Form) -> str:
-        pass
+        schema = Schema()
+        for name, field in self.fields.items():
+            schema.add_field(name, field)
+        return schema.schema
diff --git a/django_forms_as_jsonschema/schema.py b/django_forms_as_jsonschema/schema.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb8761ee056f652aa089659e5c7a63d762729d57
--- /dev/null
+++ b/django_forms_as_jsonschema/schema.py
@@ -0,0 +1,170 @@
+import copy
+import json
+
+from django import forms
+
+
+class Schema:
+    SCHEMA = {
+        "$schema": "https://json-schema.org/draft/2020-12/schema",
+        "type": "object",
+        "properties": {},
+    }
+
+    def __init__(self):
+        self.schema = copy.deepcopy(self.SCHEMA)
+
+    # example_json = {
+    #     "type": "object",
+    #     "properties": {
+    #         "stringProp": {
+    #             "type": "string",
+    #             "title": "I'm a string",
+    #             "description": "This description is used as a help message."
+    #         },
+    #         "stringTextareaProp": {
+    #             "type": "string",
+    #             "title": "I'm a string in a textarea",
+    #             "x-display": "textarea"
+    #         },
+    #         "numberProp": {
+    #             "type": "number",
+    #             "title": "I'm a number"
+    #         },
+    #         "integerProp": {
+    #             "type": "integer",
+    #             "title": "I'm an integer"
+    #         },
+    #         "integerSliderProp": {
+    #             "type": "integer",
+    #             "title": "I'm an integer in a slider",
+    #             "x-display": "slider",
+    #             "minimum": 0,
+    #             "maximum": 5
+    #         },
+    #         "booleanProp": {
+    #             "type": "boolean",
+    #             "title": "I'm a boolean",
+    #             "description": "This description is used as a help message."
+    #         },
+    #         "booleanSwitchProp": {
+    #             "type": "boolean",
+    #             "title": "I'm a boolean with switch display",
+    #             "x-display": "switch",
+    #             "description": "This description is used as a help message."
+    #         },
+    #         "stringArrayProp": {
+    #             "type": "array",
+    #             "title": "I'm an array of strings",
+    #             "items": {
+    #                 "type": "string"
+    #             }
+    #         },
+    #         "integerArrayProp": {
+    #             "type": "array",
+    #             "title": "I'm an array of integers",
+    #             "items": {
+    #                 "type": "integer"
+    #             }
+    #         }
+    #     }
+    # }
+
+    def build(self):
+        return json.dumps(self.schema)
+
+    def add_field(self, name, field):
+        type_ = "string"
+        format_ = None
+        x_display = None
+        enum = None
+
+        # string, number, integer, boolean.
+
+        match type(field.widget):
+            case forms.TextInput:
+                type_ = "string"
+
+            case forms.NumberInput:
+                type_ = "number"
+
+            case forms.EmailInput:
+                type_ = "string"
+                format_ = "email"
+
+            case forms.URLInput:
+                type_ = "string"
+                format_ = "url"
+
+            case forms.PasswordInput:
+                type_ = "string"
+                format_ = "password"
+                x_display = "password"
+
+            case forms.HiddenInput:
+                type_ = "string"
+                format_ = "hidden"
+
+            case forms.MultipleHiddenInput:
+                ...
+
+            case forms.FileInput:
+                ...
+
+            case forms.ClearableFileInput:
+                ...
+
+            case forms.Textarea:
+                x_display = "textarea"
+
+            case forms.DateInput:
+                type_ = "string"
+                format_ = "date"
+
+            case forms.DateTimeInput:
+                type_ = "string"
+                format_ = "date-time"
+
+            case forms.TimeInput:
+                type_ = "string"
+                format_ = "time"
+
+            case forms.CheckboxInput:
+                type_ = "boolean"
+
+            case forms.Select:
+                type_ = "string"
+                enum = []  # Fixme: load data from widget.options(…)
+
+            case forms.NullBooleanSelect:
+                ...
+
+            case forms.SelectMultiple:
+                ...
+
+            case forms.RadioSelect:
+                ...
+
+            case forms.CheckboxSelectMultiple:
+                ...
+
+            case forms.SplitDateTimeWidget:
+                ...
+
+            case forms.SplitHiddenDateTimeWidget:
+                ...
+
+            case forms.SelectDateWidget:
+                ...
+
+        self.schema["properties"][name] = {
+            "type": type_,
+            "title": str(field.label),
+            "description": str(field.help_text),
+        }
+        if x_display:
+            self.schema["properties"][name]["x-display"] = x_display
+        if format_:
+            self.schema["properties"][name]["format"] = format_
+        if enum:
+            self.schema["properties"][name]["enum"] = enum