diff --git a/CHANGES b/CHANGES
index cb4b0a7ce..db141384d 100644
--- a/CHANGES
+++ b/CHANGES
@@ -24,6 +24,9 @@ This release contains contributions from (alphabetically by first name):
 
 ## Modules ##
 
+ - *oemid* is a new module for configuring OEM phase-0 (image pre-mastering,
+   or pre-deployment) things. It has limited functionality at the moment,
+   writing only a single batch-identifier file.
  - All Python modules now bail out gracefully on (at least some) bad
    configurations, rather than raising an exception. The pre-release
    scripts now test for exceptions to avoid shipping modules with
diff --git a/src/modules/oemid/CMakeLists.txt b/src/modules/oemid/CMakeLists.txt
new file mode 100644
index 000000000..0c4ad03ad
--- /dev/null
+++ b/src/modules/oemid/CMakeLists.txt
@@ -0,0 +1,13 @@
+calamares_add_plugin( oemid
+    TYPE viewmodule
+    EXPORT_MACRO PLUGINDLLEXPORT_PRO
+    SOURCES
+        IDJob.cpp
+        OEMViewStep.cpp
+    UI
+        OEMPage.ui
+    LINK_PRIVATE_LIBRARIES
+        calamaresui
+        Qt5::Widgets
+    SHARED_LIB
+)
diff --git a/src/modules/oemid/IDJob.cpp b/src/modules/oemid/IDJob.cpp
new file mode 100644
index 000000000..07ce1efad
--- /dev/null
+++ b/src/modules/oemid/IDJob.cpp
@@ -0,0 +1,94 @@
+/* === This file is part of Calamares - <https://github.com/calamares> ===
+ *
+ *   Copyright 2019, Adriaan de Groot <groot@kde.org>
+ *
+ *   Calamares is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Calamares is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "IDJob.h"
+
+#include "GlobalStorage.h"
+#include "JobQueue.h"
+#include "Settings.h"
+
+#include "utils/Logger.h"
+
+#include <QDir>
+#include <QFile>
+
+IDJob::IDJob(const QString& id, QObject* parent)
+    : Job( parent )
+    , m_batchIdentifier( id )
+{
+}
+
+QString IDJob::prettyName() const
+{
+    return tr( "OEM Batch Identifier" );
+}
+
+Calamares::JobResult IDJob::writeId( const QString& dirs, const QString& filename, const QString& contents )
+{
+    if ( !QDir().mkpath( dirs ) )
+    {
+        cError() << "Could not create directories" << dirs;
+        return Calamares::JobResult::error(
+            tr( "OEM Batch Identifier" ),
+            tr( "Could not create directories <code>%1</code>." ).arg( dirs ) );
+    }
+
+    QFile output( QDir( dirs ).filePath( filename ) );
+    if ( output.exists() )
+        cWarning() << "Existing OEM Batch ID" << output.fileName() << "overwritten.";
+
+    if ( !output.open( QIODevice::WriteOnly ) )
+    {
+        cError() << "Could not write to" << output.fileName();
+        return Calamares::JobResult::error(
+            tr( "OEM Batch Identifier" ),
+            tr( "Could not open file <code>%1</code>." ).arg( output.fileName() ) );
+    }
+
+    if ( output.write( contents.toUtf8() ) < 0 )
+    {
+        cError() << "Write error on" << output.fileName();
+        return Calamares::JobResult::error(
+            tr( "OEM Batch Identifier" ),
+            tr( "Could not write to file <code>%1</code>." ).arg( output.fileName() ) );
+    }
+    output.write( "\n" );  // Ignore error on this one
+
+    return Calamares::JobResult::ok();
+}
+
+Calamares::JobResult IDJob::exec()
+{
+    cDebug() << "Setting OEM Batch ID to" << m_batchIdentifier;
+
+    Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
+
+    QString targetDir = QStringLiteral( "/var/log/installer/" );
+    QString targetFile = QStringLiteral( "oem-id" );
+    QString rootMount = gs->value( "rootMountPoint" ).toString();
+
+    static const char noRoot[] = "No rootMountPoint is set.";
+    static const char yesRoot[] = "rootMountPoint is set:";
+
+    QString targetPath;
+
+    if ( rootMount.isEmpty() && Calamares::Settings::instance()->doChroot() )
+        cWarning() << Logger::SubEntry << noRoot;
+
+    return writeId( Calamares::Settings::instance()->doChroot() ? rootMount + targetDir : targetDir, targetFile, m_batchIdentifier );
+}
diff --git a/src/modules/oemid/IDJob.h b/src/modules/oemid/IDJob.h
new file mode 100644
index 000000000..845a3f451
--- /dev/null
+++ b/src/modules/oemid/IDJob.h
@@ -0,0 +1,42 @@
+/* === This file is part of Calamares - <https://github.com/calamares> ===
+ *
+ *   Copyright 2019, Adriaan de Groot <groot@kde.org>
+ *
+ *   Calamares is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Calamares is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDJOB_H
+#define IDJOB_H
+
+#include "Job.h"
+
+#include <QString>
+
+class IDJob : public Calamares::Job
+{
+    Q_OBJECT
+public:
+    explicit IDJob( const QString& id, QObject* parent = nullptr );
+
+    virtual QString prettyName() const override;
+    virtual Calamares::JobResult exec() override;
+
+private:
+    Calamares::JobResult writeId( const QString&, const QString&, const QString& );
+
+    QString m_batchIdentifier;
+} ;
+
+
+#endif
diff --git a/src/modules/oemid/OEMPage.ui b/src/modules/oemid/OEMPage.ui
new file mode 100644
index 000000000..7406c032f
--- /dev/null
+++ b/src/modules/oemid/OEMPage.ui
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>OEMPage</class>
+ <widget class="QWidget" name="OEMPage">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>592</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+    <horstretch>1</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string notr="true">OEMPage</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0" columnstretch="1,3">
+     <item row="2" column="1">
+      <widget class="QLineEdit" name="batchIdentifier">
+       <property name="toolTip">
+        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter a batch-identifier here. This will be stored in the target system.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+       </property>
+       <property name="accessibleName">
+        <string notr="true">batch-identifier</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0" alignment="Qt::AlignRight">
+      <widget class="QLabel" name="batchIdentifier_label">
+       <property name="text">
+        <string>Ba&amp;tch:</string>
+       </property>
+       <property name="buddy">
+        <cstring>batchIdentifier</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0" colspan="2">
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;h1&gt;OEM Configuration&lt;/h1&gt;&lt;p&gt;Calamares will use OEM settings while configuring the target system.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+       </property>
+       <property name="textFormat">
+        <enum>Qt::RichText</enum>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignCenter</set>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <spacer name="verticalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeType">
+        <enum>QSizePolicy::Fixed</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/modules/oemid/OEMViewStep.cpp b/src/modules/oemid/OEMViewStep.cpp
new file mode 100644
index 000000000..0f076927b
--- /dev/null
+++ b/src/modules/oemid/OEMViewStep.cpp
@@ -0,0 +1,142 @@
+/* === This file is part of Calamares - <https://github.com/calamares> ===
+ *
+ *   Copyright 2019, Adriaan de Groot <groot@kde.org>
+ *
+ *   Calamares is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Calamares is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "OEMViewStep.h"
+
+#include "ui_OEMPage.h"
+
+#include "IDJob.h"
+
+#include "utils/Retranslator.h"
+#include "utils/Variant.h"
+
+#include <QDate>
+#include <QLabel>
+#include <QWidget>
+
+class OEMPage : public QWidget
+{
+public:
+    OEMPage()
+        : QWidget( nullptr )
+        , m_ui( new Ui_OEMPage() )
+    {
+        m_ui->setupUi( this );
+
+        CALAMARES_RETRANSLATE(
+            m_ui->retranslateUi( this );
+        )
+    }
+
+    Ui_OEMPage* m_ui;
+} ;
+
+
+OEMViewStep::OEMViewStep(QObject* parent)
+    : Calamares::ViewStep( parent )
+    , m_widget( nullptr )
+    , m_visited( false )
+{
+}
+
+OEMViewStep::~OEMViewStep()
+{
+    if ( m_widget && m_widget->parent() == nullptr )
+        m_widget->deleteLater();
+}
+
+bool OEMViewStep::isBackEnabled() const
+{
+    return true;
+}
+
+bool OEMViewStep::isNextEnabled() const
+{
+    return true;
+}
+
+bool OEMViewStep::isAtBeginning() const
+{
+    return true;
+}
+
+bool OEMViewStep::isAtEnd() const
+{
+    return true;
+}
+
+static QString substitute( QString s )
+{
+    QString t_date = QStringLiteral( "@@DATE@@" );
+    if ( s.contains( t_date ) )
+    {
+        auto date = QDate::currentDate();
+        s = s.replace( t_date, date.toString( Qt::ISODate ));
+    }
+
+    return s;
+}
+
+void OEMViewStep::onActivate()
+{
+    if ( !m_widget )
+        (void) widget();
+    if ( !m_visited && m_widget )
+        m_widget->m_ui->batchIdentifier->setText( m_user_batchIdentifier );
+    m_visited = true;
+
+    ViewStep::onActivate();
+}
+
+void OEMViewStep::onLeave()
+{
+    m_user_batchIdentifier = m_widget->m_ui->batchIdentifier->text();
+
+    ViewStep::onLeave();
+}
+
+QString OEMViewStep::prettyName() const
+{
+    return tr( "OEM Configuration" );
+}
+
+QString OEMViewStep::prettyStatus() const
+{
+    return tr( "Set the OEM Batch Identifier to <code>%1</code>." ).arg( m_user_batchIdentifier );
+}
+
+
+QWidget * OEMViewStep::widget()
+{
+    if (!m_widget)
+        m_widget = new OEMPage;
+    return m_widget;
+}
+
+Calamares::JobList OEMViewStep::jobs() const
+{
+    return Calamares::JobList() << Calamares::job_ptr( new IDJob( m_user_batchIdentifier ) );
+}
+
+void OEMViewStep::setConfigurationMap(const QVariantMap& configurationMap)
+{
+    m_conf_batchIdentifier = CalamaresUtils::getString( configurationMap, "batch-identifier" );
+    m_user_batchIdentifier = substitute( m_conf_batchIdentifier );
+}
+
+CALAMARES_PLUGIN_FACTORY_DEFINITION( OEMViewStepFactory, registerPlugin<OEMViewStep>(); )
diff --git a/src/modules/oemid/OEMViewStep.h b/src/modules/oemid/OEMViewStep.h
new file mode 100644
index 000000000..d8722594a
--- /dev/null
+++ b/src/modules/oemid/OEMViewStep.h
@@ -0,0 +1,66 @@
+/* === This file is part of Calamares - <https://github.com/calamares> ===
+ *
+ *   Copyright 2019, Adriaan de Groot <groot@kde.org>
+ *
+ *   Calamares is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Calamares is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OEMVIEWSTEP_H
+#define OEMVIEWSTEP_H
+
+#include <utils/PluginFactory.h>
+#include <viewpages/ViewStep.h>
+
+#include <PluginDllMacro.h>
+
+#include <QVariantMap>
+
+class OEMPage;
+
+class PLUGINDLLEXPORT OEMViewStep : public Calamares::ViewStep
+{
+    Q_OBJECT
+
+public:
+    explicit OEMViewStep( QObject* parent = nullptr );
+    virtual ~OEMViewStep() override;
+
+    QString prettyName() const override;
+    QString prettyStatus() const override;
+
+    QWidget* widget() override;
+
+    bool isNextEnabled() const override;
+    bool isBackEnabled() const override;
+
+    bool isAtBeginning() const override;
+    bool isAtEnd() const override;
+
+    void onActivate() override;
+    void onLeave() override;
+
+    Calamares::JobList jobs() const override;
+
+    void setConfigurationMap( const QVariantMap& configurationMap ) override;
+
+private:
+    QString m_conf_batchIdentifier;
+    QString m_user_batchIdentifier;
+    OEMPage* m_widget;
+    bool m_visited;
+};
+
+CALAMARES_PLUGIN_FACTORY_DECLARATION( OEMViewStepFactory )
+
+#endif
diff --git a/src/modules/oemid/oemid.conf b/src/modules/oemid/oemid.conf
new file mode 100644
index 000000000..8f9bc3d08
--- /dev/null
+++ b/src/modules/oemid/oemid.conf
@@ -0,0 +1,13 @@
+# This is an OEM setup (phase-0) configuration file.
+---
+# The batch-identifier is written to /var/log/installer/oem-id.
+# This value is put into the text box as the **suggested**
+# OEM ID. If @@DATE@@ is included in the identifier, then
+# that is replaced by the current date in yyyy-MM-dd (ISO) format.
+#
+# it is ok for the identifier to be empty.
+#
+# The identifier is written to the file as UTF-8 (this will be no
+# different from ASCII, for most inputs) and followed by a newline.
+# If the identifier is empty, only a newline is written.
+batch-identifier: neon-@@DATE@@