diff --git a/src/pystencilssfg/__init__.py b/src/pystencilssfg/__init__.py
index b5ac38f9c9487c6b8caf77a72cda55ea7fc1e792..a888fc45ce84f318dfd51e92d74c04fda18d265a 100644
--- a/src/pystencilssfg/__init__.py
+++ b/src/pystencilssfg/__init__.py
@@ -1,4 +1,5 @@
 from .configuration import SfgConfiguration, SfgOutputMode, SfgCodeStyle
+from .config import SfgConfig
 from .generator import SourceFileGenerator
 from .composer import SfgComposer
 from .context import SfgContext
@@ -6,6 +7,7 @@ from .lang import SfgVar, AugExpr
 from .exceptions import SfgException
 
 __all__ = [
+    "SfgConfig",
     "SourceFileGenerator",
     "SfgComposer",
     "SfgConfiguration",
diff --git a/src/pystencilssfg/config.py b/src/pystencilssfg/config.py
index 051cf8dcedbd378851ad1f6cdfb70e8131f94a31..992233999a033b3596f722f950caba26206dacc4 100644
--- a/src/pystencilssfg/config.py
+++ b/src/pystencilssfg/config.py
@@ -1,6 +1,9 @@
 from __future__ import annotations
 
-from typing import Generic, TypeVar, Callable, Any
+from argparse import ArgumentParser
+
+from types import ModuleType
+from typing import Generic, TypeVar, Callable, Any, Sequence
 from abc import ABC
 from dataclasses import dataclass, fields, field
 from enum import Enum, auto
@@ -104,7 +107,7 @@ class FileExtensions(ConfigBase):
         return ext
 
 
-class SfgOutputMode(Enum):
+class OutputMode(Enum):
     STANDALONE = auto()
     """Generate a header/implementation file pair (e.g. ``.hpp/.cpp``) where the implementation file will
     be compiled to a standalone object."""
@@ -124,6 +127,7 @@ class SfgOutputMode(Enum):
 @dataclass
 class CodeStyle(ConfigBase):
     indent_width: Option[int] = Option(2)
+    """The number of spaces successively nested blocks should be indented with"""
 
     code_style: Option[str] = Option("file")
     """Code style to be used by clang-format. Passed verbatim to `--style` argument of the clang-format CLI.
@@ -153,7 +157,7 @@ class SfgConfig(ConfigBase):
     extensions: FileExtensions = field(default_factory=FileExtensions)
     """File extensions of the generated files"""
 
-    output_mode: Option[SfgOutputMode] = Option(SfgOutputMode.STANDALONE)
+    output_mode: Option[OutputMode] = Option(OutputMode.STANDALONE)
     """The generator's output mode; defines which files to generate, and the set of legal file extensions."""
 
     outer_namespace: Option[str | _GlobalNamespace] = Option(GLOBAL_NAMESPACE)
@@ -167,6 +171,158 @@ class SfgConfig(ConfigBase):
     """Directory to which the generated files should be written."""
 
 
+class CommandLineParameters:
+    @staticmethod
+    def add_args_to_parser(parser: ArgumentParser):
+        config_group = parser.add_argument_group("Configuration")
+
+        config_group.add_argument(
+            "--sfg-output-dir", type=str, default=None, dest="output_directory"
+        )
+        config_group.add_argument(
+            "--sfg-file-extensions",
+            type=str,
+            default=None,
+            dest="file_extensions",
+            help="Comma-separated list of file extensions",
+        )
+        config_group.add_argument(
+            "--sfg-output-mode",
+            type=str,
+            default=None,
+            choices=("standalone", "inline", "header-only"),
+            dest="output_mode",
+        )
+        config_group.add_argument(
+            "--sfg-config-module", type=str, default=None, dest="config_module_path"
+        )
+
+        return parser
+
+    def __init__(self, args):
+        self._cl_config_module_path: str | None = args.config_module_path
+
+        if args.output_mode is not None:
+            match args.output_mode.lower():
+                case "standalone":
+                    output_mode = OutputMode.STANDALONE
+                case "inline":
+                    output_mode = OutputMode.INLINE
+                case "header-only":
+                    output_mode = OutputMode.HEADER_ONLY
+                case _:
+                    assert False, "invalid output mode"
+        else:
+            output_mode = None
+
+        self._cl_output_mode = output_mode
+
+        self._cl_output_dir: str | None = args.output_directory
+
+        if args.file_extensions is not None:
+            file_extentions = list(args.file_extensions.split(","))
+            h_ext, impl_ext = self._get_file_extensions(file_extentions)
+            self._cl_header_ext = h_ext
+            self._cl_impl_ext = impl_ext
+        else:
+            self._cl_header_ext = None
+            self._cl_impl_ext = None
+
+        if self._cl_config_module_path is not None:
+            self._config_module = self._import_config_module(
+                self._cl_config_module_path
+            )
+        else:
+            self._config_module = None
+
+    @property
+    def configuration_module(self) -> ModuleType | None:
+        return self._config_module
+
+    def get_config(self) -> SfgConfig:
+        cfg = SfgConfig()
+        if self._config_module is not None and hasattr(
+            self._config_module, "configure_sfg"
+        ):
+            self._config_module.configure_sfg(cfg)
+
+        if self._cl_output_mode is not None:
+            cfg.output_mode = self._cl_output_mode
+        if self._cl_header_ext is not None:
+            cfg.extensions.header = self._cl_header_ext
+        if self._cl_impl_ext is not None:
+            cfg.extensions.impl = self._cl_impl_ext
+        if self._cl_output_dir is not None:
+            cfg.output_directory = self._cl_output_dir
+
+        return cfg
+
+    def find_conflicts(self, cfg: SfgConfig):
+        for name, mine, theirs in (
+            ("output_mode", self._cl_output_mode, cfg.output_mode),
+            ("extensions.header", self._cl_header_ext, cfg.extensions.header),
+            ("extensions.impl", self._cl_impl_ext, cfg.extensions.impl),
+            ("output_directory", self._cl_output_dir, cfg.output_directory),
+        ):
+            if mine is not None and theirs is not None and mine != theirs:
+                raise SfgConfigException(
+                    f"Conflicting values given for option {name} on command line and inside generator script.\n"
+                    f"    Value on command-line: {name}",
+                    f"    Value in script: {name}",
+                )
+
+    def get_project_info(self) -> Any:
+        if self._config_module is not None and hasattr(
+            self._config_module, "project_info"
+        ):
+            return self._config_module.project_info()
+        else:
+            return None
+
+    def _get_file_extensions(self, extensions: Sequence[str]):
+        h_ext = None
+        src_ext = None
+
+        extensions = tuple((ext[1:] if ext[0] == "." else ext) for ext in extensions)
+
+        HEADER_FILE_EXTENSIONS = {"h", "hpp", "hxx", "h++", "cuh"}
+        IMPL_FILE_EXTENSIONS = {"c", "cpp", "cxx", "c++", "cu", ".impl.h", "ipp"}
+
+        for ext in extensions:
+            if ext in HEADER_FILE_EXTENSIONS:
+                if h_ext is not None:
+                    raise SfgConfigException(
+                        "Multiple header file extensions specified."
+                    )
+                h_ext = ext
+            elif ext in IMPL_FILE_EXTENSIONS:
+                if src_ext is not None:
+                    raise SfgConfigException(
+                        "Multiple source file extensions specified."
+                    )
+                src_ext = ext
+            else:
+                raise SfgConfigException(
+                    f"Invalid file extension: Don't know what to do with '.{ext}'"
+                )
+
+        return h_ext, src_ext
+
+    def _import_config_module(self, module_path: str) -> ModuleType:
+        cfg_modulename = path.splitext(path.split(module_path)[1])[0]
+
+        cfg_spec = iutil.spec_from_file_location(cfg_modulename, module_path)
+
+        if cfg_spec is None:
+            raise SfgConfigException(
+                f"Unable to import configuration module {module_path}",
+            )
+
+        config_module = iutil.module_from_spec(cfg_spec)
+        cfg_spec.loader.exec_module(config_module)
+        return config_module
+
+
 def run_configuration_module(configurator_script: str) -> SfgConfig:
     """Run a configuration module.
 
diff --git a/src/pystencilssfg/context.py b/src/pystencilssfg/context.py
index bd3591889cbed6ab2a4bc023787944857585a601..17537a26706be5e5a9c93bc9cc5c09bb13c48dff 100644
--- a/src/pystencilssfg/context.py
+++ b/src/pystencilssfg/context.py
@@ -1,6 +1,6 @@
 from typing import Generator, Sequence, Any
 
-from .configuration import SfgCodeStyle
+from .config import CodeStyle
 from .ir.source_components import (
     SfgHeaderInclude,
     SfgKernelNamespace,
@@ -45,7 +45,7 @@ class SfgContext:
     def __init__(
         self,
         outer_namespace: str | None = None,
-        codestyle: SfgCodeStyle = SfgCodeStyle(),
+        codestyle: CodeStyle | None = None,
         argv: Sequence[str] | None = None,
         project_info: Any = None,
     ):
@@ -65,7 +65,7 @@ class SfgContext:
         self._outer_namespace = outer_namespace
         self._inner_namespace: str | None = None
 
-        self._codestyle = codestyle
+        self._codestyle = codestyle if codestyle is not None else CodeStyle()
 
         #   Source Components
         self._prelude: str = ""
@@ -121,7 +121,7 @@ class SfgContext:
                 assert False
 
     @property
-    def codestyle(self) -> SfgCodeStyle:
+    def codestyle(self) -> CodeStyle:
         """The code style object for this generation context."""
         return self._codestyle
 
diff --git a/src/pystencilssfg/emission/__init__.py b/src/pystencilssfg/emission/__init__.py
index 3280e459ff72a23bea06e711e5fb0aa73989cc74..fd666283edd12d49c02eb42eee90a5ae8f9756dd 100644
--- a/src/pystencilssfg/emission/__init__.py
+++ b/src/pystencilssfg/emission/__init__.py
@@ -1,5 +1,5 @@
-from .emitter import AbstractEmitter
+from .emitter import AbstractEmitter, OutputSpec
 from .header_impl_pair import HeaderImplPairEmitter
 from .header_only import HeaderOnlyEmitter
 
-__all__ = ["AbstractEmitter", "HeaderImplPairEmitter", "HeaderOnlyEmitter"]
+__all__ = ["AbstractEmitter", "OutputSpec", "HeaderImplPairEmitter", "HeaderOnlyEmitter"]
diff --git a/src/pystencilssfg/emission/emitter.py b/src/pystencilssfg/emission/emitter.py
index 55fe43c337069e49d38ed27a85e2f33929dfdc54..e96e1e187bb55fcaca892c60f751649a90e4c6a7 100644
--- a/src/pystencilssfg/emission/emitter.py
+++ b/src/pystencilssfg/emission/emitter.py
@@ -1,9 +1,42 @@
 from typing import Sequence
 from abc import ABC, abstractmethod
+from dataclasses import dataclass
+from os import path
 
 from ..context import SfgContext
 
 
+@dataclass
+class OutputSpec:
+    """Name and path specification for files output by the code generator.
+
+    Filenames are constructed as `<output_directory>/<basename>.<extension>`."""
+
+    output_directory: str
+    """Directory to which the generated files should be written."""
+
+    basename: str
+    """Base name for output files."""
+
+    header_extension: str
+    """File extension for generated header file."""
+
+    impl_extension: str
+    """File extension for generated implementation file."""
+
+    def get_header_filename(self):
+        return f"{self.basename}.{self.header_extension}"
+
+    def get_impl_filename(self):
+        return f"{self.basename}.{self.impl_extension}"
+
+    def get_header_filepath(self):
+        return path.join(self.output_directory, self.get_header_filename())
+
+    def get_impl_filepath(self):
+        return path.join(self.output_directory, self.get_impl_filename())
+
+
 class AbstractEmitter(ABC):
     @property
     @abstractmethod
diff --git a/src/pystencilssfg/emission/header_impl_pair.py b/src/pystencilssfg/emission/header_impl_pair.py
index 8d2cd2cf437e0dfbfa8527d6f288d291c27e0a83..9e1e69729418240844f192777bdd609a054f3614 100644
--- a/src/pystencilssfg/emission/header_impl_pair.py
+++ b/src/pystencilssfg/emission/header_impl_pair.py
@@ -1,19 +1,18 @@
 from typing import Sequence
 from os import path, makedirs
 
-from ..configuration import SfgOutputSpec
 from ..context import SfgContext
 from .prepare import prepare_context
 from .printers import SfgHeaderPrinter, SfgImplPrinter
 from .clang_format import invoke_clang_format
 
-from .emitter import AbstractEmitter
+from .emitter import AbstractEmitter, OutputSpec
 
 
 class HeaderImplPairEmitter(AbstractEmitter):
     """Emits a header-implementation file pair."""
 
-    def __init__(self, output_spec: SfgOutputSpec, inline_impl: bool = False):
+    def __init__(self, output_spec: OutputSpec, inline_impl: bool = False):
         """Create a `HeaderImplPairEmitter` from an [SfgOutputSpec][pystencilssfg.configuration.SfgOutputSpec]."""
         self._basename = output_spec.basename
         self._output_directory = output_spec.output_directory
diff --git a/src/pystencilssfg/emission/header_only.py b/src/pystencilssfg/emission/header_only.py
index 7347a61059ac48ad2f5b7c320aa3a2965c5b8452..169d2bc00240ac2898234aa657614e45ebe58857 100644
--- a/src/pystencilssfg/emission/header_only.py
+++ b/src/pystencilssfg/emission/header_only.py
@@ -1,17 +1,16 @@
 from typing import Sequence
 from os import path, makedirs
 
-from ..configuration import SfgOutputSpec
 from ..context import SfgContext
 from .prepare import prepare_context
 from .printers import SfgHeaderPrinter
 from .clang_format import invoke_clang_format
 
-from .emitter import AbstractEmitter
+from .emitter import AbstractEmitter, OutputSpec
 
 
 class HeaderOnlyEmitter(AbstractEmitter):
-    def __init__(self, output_spec: SfgOutputSpec):
+    def __init__(self, output_spec: OutputSpec):
         """Create a `HeaderImplPairEmitter` from an [SfgOutputSpec][pystencilssfg.configuration.SfgOutputSpec]."""
         self._basename = output_spec.basename
         self._output_directory = output_spec.output_directory
diff --git a/src/pystencilssfg/generator.py b/src/pystencilssfg/generator.py
index e7b0f5c24a71f0a41a15b559332e0d3d94817ad0..c2461800348c570a02b5cdc52de880d106b3a6c4 100644
--- a/src/pystencilssfg/generator.py
+++ b/src/pystencilssfg/generator.py
@@ -2,15 +2,10 @@ import sys
 import os
 from os import path
 
-from .configuration import (
-    SfgConfiguration,
-    SfgOutputMode,
-    config_from_commandline,
-    merge_configurations,
-)
+from .config import SfgConfig, CommandLineParameters, OutputMode, GLOBAL_NAMESPACE
 from .context import SfgContext
 from .composer import SfgComposer
-from .emission import AbstractEmitter
+from .emission import AbstractEmitter, OutputSpec
 
 
 class SourceFileGenerator:
@@ -32,8 +27,8 @@ class SourceFileGenerator:
         sfg_config: User configuration for the code generator
     """
 
-    def __init__(self, sfg_config: SfgConfiguration | None = None):
-        if sfg_config and not isinstance(sfg_config, SfgConfiguration):
+    def __init__(self, sfg_config: SfgConfig | None = None):
+        if sfg_config and not isinstance(sfg_config, SfgConfig):
             raise TypeError("sfg_config is not an SfgConfiguration.")
 
         import __main__
@@ -42,17 +37,27 @@ class SourceFileGenerator:
         scriptname = path.split(scriptpath)[1]
         basename = path.splitext(scriptname)[0]
 
-        project_config, cmdline_config, script_args = config_from_commandline(sys.argv)
+        from argparse import ArgumentParser
 
-        config = merge_configurations(project_config, cmdline_config, sfg_config)
+        parser = ArgumentParser(
+            scriptname,
+            description="Generator script using pystencils-sfg",
+            allow_abbrev=False,
+        )
+        CommandLineParameters.add_args_to_parser(parser)
+        sfg_args, script_args = parser.parse_known_args()
+        cli_params = CommandLineParameters(sfg_args)
 
-        assert config.codestyle is not None
+        config = cli_params.get_config()
+        if sfg_config is not None:
+            cli_params.find_conflicts(sfg_config)
+            config.override(sfg_config)
 
         self._context = SfgContext(
-            config.outer_namespace,
+            None if config.outer_namespace is GLOBAL_NAMESPACE else config.outer_namespace,  # type: ignore
             config.codestyle,
             argv=script_args,
-            project_info=config.project_info,
+            project_info=cli_params.get_project_info(),
         )
 
         from pystencilssfg.ir import SfgHeaderInclude
@@ -60,22 +65,29 @@ class SourceFileGenerator:
         self._context.add_include(SfgHeaderInclude("cstdint", system_header=True))
         self._context.add_definition("#define RESTRICT __restrict__")
 
+        output_spec = OutputSpec(
+            config.get_option("output_directory"),
+            basename,
+            config.extensions.get_option("header"),
+            config.extensions.get_option("impl"),
+        )
+
         self._emitter: AbstractEmitter
-        match config.output_mode:
-            case SfgOutputMode.HEADER_ONLY:
+        match config.get_option("output_mode"):
+            case OutputMode.HEADER_ONLY:
                 from .emission import HeaderOnlyEmitter
 
-                self._emitter = HeaderOnlyEmitter(config.get_output_spec(basename))
-            case SfgOutputMode.INLINE:
+                self._emitter = HeaderOnlyEmitter(output_spec)
+            case OutputMode.INLINE:
                 from .emission import HeaderImplPairEmitter
 
                 self._emitter = HeaderImplPairEmitter(
-                    config.get_output_spec(basename), inline_impl=True
+                    output_spec, inline_impl=True
                 )
-            case SfgOutputMode.STANDALONE:
+            case OutputMode.STANDALONE:
                 from .emission import HeaderImplPairEmitter
 
-                self._emitter = HeaderImplPairEmitter(config.get_output_spec(basename))
+                self._emitter = HeaderImplPairEmitter(output_spec)
 
     def clean_files(self):
         for file in self._emitter.output_files:
diff --git a/tests/generator/data/project_config.py b/tests/generator/data/project_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..73e5e9c6d52d979542b1f31137f8f31cf5e0c637
--- /dev/null
+++ b/tests/generator/data/project_config.py
@@ -0,0 +1,13 @@
+from pystencilssfg import SfgConfig
+
+
+def configure_sfg(cfg: SfgConfig):
+    cfg.codestyle.code_style = "llvm"
+    cfg.codestyle.indent_width = 3
+    cfg.output_directory = "generated_sources"
+    cfg.outer_namespace = "myproject"
+    cfg.extensions.header = "hpp"
+
+
+magic_string = "Spam and eggs"
+magic_number = 0xcafe
diff --git a/tests/generator/test_config.py b/tests/generator/test_config.py
index d7d38bd5020e583a2f74afe6178025665f2c7a23..879d47f71934c87d120a71cbb856f036e00ecbee 100644
--- a/tests/generator/test_config.py
+++ b/tests/generator/test_config.py
@@ -1,10 +1,21 @@
-from pystencilssfg.config import SfgConfig, SfgOutputMode, GLOBAL_NAMESPACE
+from os import path
+
+from pystencilssfg import SourceFileGenerator
+from pystencilssfg.config import (
+    SfgConfig,
+    OutputMode,
+    GLOBAL_NAMESPACE,
+    CommandLineParameters,
+)
+
+
+DATA_DIR = path.join(path.split(__file__)[0], "data")
 
 
 def test_defaults():
     cfg = SfgConfig()
 
-    assert cfg.get_option("output_mode") == SfgOutputMode.STANDALONE
+    assert cfg.get_option("output_mode") == OutputMode.STANDALONE
     assert cfg.extensions.get_option("header") == "hpp"
     assert cfg.codestyle.get_option("indent_width") == 2
     assert cfg.get_option("outer_namespace") is GLOBAL_NAMESPACE
@@ -42,3 +53,64 @@ def test_override():
     assert cfg1.codestyle.indent_width is None
     assert cfg1.codestyle.code_style is None
     assert cfg1.codestyle.clang_format_binary == "bogus"
+
+
+def test_from_commandline():
+    from argparse import ArgumentParser
+
+    parser = ArgumentParser()
+    CommandLineParameters.add_args_to_parser(parser)
+
+    args = parser.parse_args(
+        ["--sfg-output-dir", ".out", "--sfg-file-extensions", ".h++,c++"]
+    )
+
+    cli_args = CommandLineParameters(args)
+    cfg = cli_args.get_config()
+
+    assert cfg.output_directory == ".out"
+    assert cfg.extensions.header == "h++"
+    assert cfg.extensions.impl == "c++"
+
+    config_module = path.join(DATA_DIR, "project_config.py")
+    args = parser.parse_args(
+        ["--sfg-output-dir", "gen_sources", "--sfg-config-module", config_module]
+    )
+    cli_args = CommandLineParameters(args)
+    cfg = cli_args.get_config()
+
+    assert cfg.codestyle.code_style == "llvm"
+    assert cfg.codestyle.indent_width == 3
+    assert (
+        cfg.output_directory == "gen_sources"
+    )  # value from config module overridden by commandline
+    assert cfg.outer_namespace == "myproject"
+    assert cfg.extensions.header == "hpp"
+
+    assert cli_args.configuration_module is not None
+    assert cli_args.configuration_module.magic_string == "Spam and eggs"
+    assert cli_args.configuration_module.magic_number == 0xCAFE
+
+
+def test_generator_config(monkeypatch, tmp_path):
+    import sys
+
+    config_module = path.join(DATA_DIR, "project_config.py")
+    args = [
+        "genscript.py",
+        "--sfg-file-extensions",
+        ".c++,.h++",
+        "--sfg-config-module",
+        config_module,
+        "test1",
+        "test2",
+    ]
+    monkeypatch.setattr(sys, "argv", args)
+
+    cfg = SfgConfig(output_directory=str(tmp_path.absolute()))
+
+    with SourceFileGenerator(cfg) as sfg:
+        ctx = sfg.context
+
+        assert ctx.outer_namespace == "myproject"
+        assert ctx.argv == ["test1", "test2"]