diff --git a/src/pystencils/nbackend/exceptions.py b/src/pystencils/nbackend/exceptions.py
index 8e48653047d70ccac3fea1d490871ff861510e9b..6fe83b3f9dad9f7cfe07dd90b65a887962b2e251 100644
--- a/src/pystencils/nbackend/exceptions.py
+++ b/src/pystencils/nbackend/exceptions.py
@@ -2,5 +2,13 @@ class PsInternalCompilerError(Exception):
     pass
 
 
+class PsOptionsError(Exception):
+    pass
+
+
+class PsInputError(Exception):
+    pass
+
+
 class PsMalformedAstException(Exception):
     pass
diff --git a/src/pystencils/nbackend/kernelcreation/context.py b/src/pystencils/nbackend/kernelcreation/context.py
index 8d14a5d4f82cc61ec9d0be4a4ec5130bdcfd8b74..40cd2448f27ad08a03b35e66221d4f30cb6dab73 100644
--- a/src/pystencils/nbackend/kernelcreation/context.py
+++ b/src/pystencils/nbackend/kernelcreation/context.py
@@ -3,7 +3,7 @@ from typing import cast
 from dataclasses import dataclass
 
 
-from ...field import Field
+from ...field import Field, FieldType
 from ...typing import TypedSymbol, BasicType
 
 from ..arrays import PsLinearizedArray
@@ -12,13 +12,16 @@ from ..types.quick import make_type
 from ..constraints import PsKernelConstraint
 from ..exceptions import PsInternalCompilerError
 
+from .options import KernelCreationOptions
 from .iteration_space import IterationSpace, FullIterationSpace, SparseIterationSpace
 
 
 @dataclass
-class PsArrayDescriptor:
-    field: Field
-    array: PsLinearizedArray
+class FieldsInKernel:
+    domain_fields: set[Field] = set()
+    index_fields: set[Field] = set()
+    custom_fields: set[Field] = set()
+    buffer_fields: set[Field] = set()
 
 
 class KernelCreationContext:
@@ -44,16 +47,21 @@ class KernelCreationContext:
     or full iteration space.
     """
 
-    def __init__(self, index_dtype: PsIntegerType):
-        self._index_dtype = index_dtype
+    def __init__(self, options: KernelCreationOptions):
+        self._options = options
         self._arrays: dict[Field, PsLinearizedArray] = dict()
         self._constraints: list[PsKernelConstraint] = []
 
+        self._fields_collection = FieldsInKernel()
         self._ispace: IterationSpace | None = None
 
+    @property
+    def options(self) -> KernelCreationOptions:
+        return self._options
+
     @property
     def index_dtype(self) -> PsIntegerType:
-        return self._index_dtype
+        return self._options.index_dtype
 
     def add_constraints(self, *constraints: PsKernelConstraint):
         self._constraints += constraints
@@ -62,6 +70,24 @@ class KernelCreationContext:
     def constraints(self) -> tuple[PsKernelConstraint, ...]:
         return tuple(self._constraints)
 
+    @property
+    def fields(self) -> FieldsInKernel:
+        return self._fields_collection
+
+    def add_field(self, field: Field):
+        """Add the given field to the context's fields collection"""
+        match field.field_type:
+            case FieldType.GENERIC | FieldType.STAGGERED | FieldType.STAGGERED_FLUX:
+                self._fields_collection.domain_fields.add(field)
+            case FieldType.BUFFER:
+                self._fields_collection.buffer_fields.add(field)
+            case FieldType.INDEXED:
+                self._fields_collection.index_fields.add(field)
+            case FieldType.CUSTOM:
+                self._fields_collection.custom_fields.add(field)
+            case _:
+                assert False, "unreachable code"
+
     def get_array(self, field: Field) -> PsLinearizedArray:
         if field not in self._arrays:
             arr_shape = tuple(
@@ -90,6 +116,8 @@ class KernelCreationContext:
         return self._arrays[field]
 
     def set_iteration_space(self, ispace: IterationSpace):
+        if self._ispace is not None:
+            raise PsInternalCompilerError("Iteration space was already set.")
         self._ispace = ispace
 
     def get_iteration_space(self) -> IterationSpace:
diff --git a/src/pystencils/nbackend/kernelcreation/domain_kernels.py b/src/pystencils/nbackend/kernelcreation/domain_kernels.py
index 1ee1cfae95463b39261b75a0dc6557e9431fe203..18f157c09bcde5f28775c0d75dd4eef73cfd0d8d 100644
--- a/src/pystencils/nbackend/kernelcreation/domain_kernels.py
+++ b/src/pystencils/nbackend/kernelcreation/domain_kernels.py
@@ -1,47 +1,55 @@
-from types import EllipsisType
+from typing import Sequence
+from itertools import chain
 
 from ...simp import AssignmentCollection
-from ...field import Field
+from ...field import Field, FieldType
 from ...kernel_contrains_check import KernelConstraintsCheck
 
-from ..types.quick import SInt
 from ..ast import PsBlock
 
-from .context import KernelCreationContext, FullIterationSpace
+from .context import KernelCreationContext, IterationSpace
 from .freeze import FreezeExpressions
 from .typification import Typifier
+from .options import KernelCreationOptions
+from ..exceptions import PsInputError, PsInternalCompilerError
 
 # flake8: noqa
 
 
-def create_domain_kernel(assignments: AssignmentCollection):
-    #   TODO: Assemble configuration
-
+def create_kernel(assignments: AssignmentCollection, options: KernelCreationOptions):
     #   1. Prepare context
-    ctx = KernelCreationContext(SInt(64))  # TODO: how to determine index type?
+    ctx = KernelCreationContext(options)
 
     #   2. Check kernel constraints and collect all fields
+
+    """
+    TODO: Replace the KernelConstraintsCheck by a KernelAnalysis pass.
+
+    The kernel analysis should:
+     - Check constraints on the assignments (SSA form, independence conditions, ...)
+     - Collect all fields and register them in the context
+     - Maybe collect all field accesses and register them at the context
+     
+    Running all this analysis in a single pass will likely improve compiler performance
+    since no additional searches, e.g. for field accesses, are necessary later.
+    """
+
     check = KernelConstraintsCheck()  # TODO: config
     check.visit(assignments)
 
-    #   All steps up to this point are the same in domain and indexed kernels;
-    #   the difference now comes with the iteration space.
-    #
-    #   Domain kernels create a full iteration space from their iteration slice
-    #   which is either explicitly given or computed from ghost layer requirements.
-    #   Indexed kernels, on the other hand, have to create a sparse iteration space
-    #   from one index list.
+    #   Collect all fields
+    for f in chain(check.fields_written, check.fields_read):
+        ctx.add_field(f)
 
     #   3. Create iteration space
-    ghost_layers: int = NotImplemented  # determine required ghost layers
-    common_shape: tuple[
-        int | EllipsisType, ...
-    ] = NotImplemented  # unify field shapes, add parameter constraints
-    #   don't forget custom iteration slice
-    ispace: FullIterationSpace = (
-        NotImplemented  # create from ghost layers and with given shape
+    ispace: IterationSpace = (
+        create_sparse_iteration_space(ctx, assignments)
+        if len(ctx.fields.index_fields) > 0
+        else create_full_iteration_space(ctx, assignments)
     )
 
+    ctx.set_iteration_space(ispace)
+
     #   4. Freeze assignments
     #   This call is the same for both domain and indexed kernels
     freeze = FreezeExpressions(ctx)
@@ -66,3 +74,63 @@ def create_domain_kernel(assignments: AssignmentCollection):
     #     - Loop Splitting, Tiling, Blocking
 
     #   8. Create and return kernel function.
+
+
+def create_sparse_iteration_space(
+    ctx: KernelCreationContext, assignments: AssignmentCollection
+) -> IterationSpace:
+    return NotImplemented
+
+
+def create_full_iteration_space(
+    ctx: KernelCreationContext, assignments: AssignmentCollection
+) -> IterationSpace:
+    assert not ctx.fields.index_fields
+
+    #   Collect all relative accesses into domain fields
+    def access_filter(acc: Field.Access):
+        return acc.field.field_type in (
+            FieldType.GENERIC,
+            FieldType.STAGGERED,
+            FieldType.STAGGERED_FLUX,
+        )
+
+    domain_field_accesses = assignments.atoms(Field.Access)
+    domain_field_accesses = set(filter(access_filter, domain_field_accesses))
+
+    # The following scenarios exist:
+    # - We have at least one domain field -> find the common field and use it to determine the iteration region
+    # - We have no domain fields, but at least one custom field -> determine common field from custom fields
+    # - We have neither domain nor custom fields -> Error
+
+    from ...transformations import get_common_field
+
+    if len(domain_field_accesses) > 0:
+        archetype_field = get_common_field(ctx.fields.domain_fields)
+        inferred_gls = max(
+            [fa.required_ghost_layers for fa in domain_field_accesses]
+        )
+    elif len(ctx.fields.custom_fields) > 0:
+        archetype_field = get_common_field(ctx.fields.custom_fields)
+        inferred_gls = 0
+    else:
+        raise PsInputError(
+            "Unable to construct iteration space: The kernel contains no accesses to domain or custom fields."
+        )
+
+    # If the user provided a ghost layer specification, use that
+    # Otherwise, if an iteration slice was specified, use that
+    # Otherwise, use the inferred ghost layers
+
+    from .iteration_space import FullIterationSpace
+
+    if ctx.options.ghost_layers is not None:
+        return FullIterationSpace.create_with_ghost_layers(
+            ctx, archetype_field, ctx.options.ghost_layers
+        )
+    elif ctx.options.iteration_slice is not None:
+        raise PsInternalCompilerError("Iteration slices not supported yet")
+    else:
+        return FullIterationSpace.create_with_ghost_layers(
+            ctx, archetype_field, inferred_gls
+        )
diff --git a/src/pystencils/nbackend/kernelcreation/iteration_space.py b/src/pystencils/nbackend/kernelcreation/iteration_space.py
index ade56a04ca1fb62d31512c8ab11fee6da857f9de..1cabc6736209398e7cd10551c0ae7f980caa0f60 100644
--- a/src/pystencils/nbackend/kernelcreation/iteration_space.py
+++ b/src/pystencils/nbackend/kernelcreation/iteration_space.py
@@ -6,7 +6,6 @@ from functools import reduce
 from operator import mul
 
 from ...field import Field
-from ...transformations import get_common_field
 
 from ..typed_expressions import (
     PsTypedVariable,
@@ -57,14 +56,13 @@ class FullIterationSpace(IterationSpace):
     @staticmethod
     def create_with_ghost_layers(
         ctx: KernelCreationContext,
-        fields: set[Field],
+        archetype_field: Field,
         ghost_layers: int | Sequence[int | tuple[int, int]],
     ) -> FullIterationSpace:
         """Create an iteration space for a collection of fields with ghost layers."""
 
-        repr_field: Field = get_common_field(fields)
-        repr_arr = ctx.get_array(repr_field)
-        dim = repr_field.spatial_dimensions
+        archetype_array = ctx.get_array(archetype_field)
+        dim = archetype_field.spatial_dimensions
         counters = [
             PsTypedVariable(name, ctx.index_dtype)
             for name in Defaults.spatial_counter_names
@@ -92,7 +90,7 @@ class FullIterationSpace(IterationSpace):
         dimensions = [
             FullIterationSpace.Dimension(gl_left, shape - gl_right, one, ctr)
             for (gl_left, gl_right), shape, ctr in zip(
-                ghost_layer_exprs, repr_arr.shape, counters, strict=True
+                ghost_layer_exprs, archetype_array.shape, counters, strict=True
             )
         ]
 
diff --git a/src/pystencils/nbackend/kernelcreation/options.py b/src/pystencils/nbackend/kernelcreation/options.py
new file mode 100644
index 0000000000000000000000000000000000000000..c9efd83c489eb9627bbf8883dcc1cd59e18adbca
--- /dev/null
+++ b/src/pystencils/nbackend/kernelcreation/options.py
@@ -0,0 +1,54 @@
+from typing import Sequence
+from dataclasses import dataclass
+
+from ...enums import Target
+from ..exceptions import PsOptionsError
+from ..types import PsIntegerType
+
+from .defaults import Sympy as SpDefaults
+
+
+@dataclass
+class KernelCreationOptions:
+    """Options for create_kernel."""
+
+    target: Target = Target.CPU
+    """The code generation target.
+    
+    TODO: Enhance `Target` from enum to a larger target spec, e.g. including vectorization architecture, ...
+    """
+
+    function_name: str = "kernel"
+    """Name of the generated function"""
+
+    ghost_layers: None | int | Sequence[int | tuple[int, int]] = None
+    """Specifies the number of ghost layers of the iteration region.
+    
+    Options:
+     - `None`: Required ghost layers are inferred from field accesses
+     - `int`:  A uniform number of ghost layers in each spatial coordinate is applied
+     - `Sequence[int, tuple[int, int]]`: Ghost layers are specified for each spatial coordinate.
+        In each coordinate, a single integer specifies the ghost layers at both the lower and upper iteration limit,
+        while a pair of integers specifies the lower and upper ghost layers separately.
+
+    When manually specifying ghost layers, it is the user's responsibility to avoid out-of-bounds memory accesses.
+    If `ghost_layers=None` is specified, the iteration region may otherwise be set using the `iteration_slice` option.
+    """
+
+    iteration_slice: None | tuple[slice, ...] = None
+    """Specifies the kernel's iteration slice.
+    
+    `iteration_slice` may only be set if `ghost_layers = None`.
+    If it is set, a slice must be specified for each spatial coordinate.
+    TODO: Specification of valid slices and their behaviour
+    """
+
+    index_dtype: PsIntegerType = SpDefaults.index_dtype
+    """Data type used for all index calculations."""
+
+    def __post_init__(self):
+        if self.iteration_slice is not None and self.ghost_layers is not None:
+            raise PsOptionsError(
+                "Parameters `iteration_slice` and `ghost_layers` are mutually exclusive; "
+                "at most one of them may be set."
+            )