diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm
index 370bac2ed..5ae6ac08f 100644
--- a/lib/Slic3r/GUI/MainFrame.pm
+++ b/lib/Slic3r/GUI/MainFrame.pm
@@ -422,6 +422,7 @@ sub quick_slice {
         if ($params{reslice}) {
             $output_file = $qs_last_output_file if defined $qs_last_output_file;
         } elsif ($params{save_as}) {
+            # The following line may die if the output_filename_format template substitution fails.
             $output_file = $sprint->output_filepath;
             $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg};
             my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:',
diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
index aeefbe6a3..0cb433309 100644
--- a/lib/Slic3r/GUI/Plater.pm
+++ b/lib/Slic3r/GUI/Plater.pm
@@ -1291,9 +1291,11 @@ sub export_gcode {
     
     # select output file
     if ($output_file) {
-        $self->{export_gcode_output_file} = $self->{print}->output_filepath($output_file);
+        $self->{export_gcode_output_file} = eval { $self->{print}->output_filepath($output_file) };
+        Slic3r::GUI::catch_error($self) and return;
     } else {
-        my $default_output_file = $self->{print}->output_filepath($main::opt{output} // '');
+        my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') };
+        Slic3r::GUI::catch_error($self) and return;
         my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', 
             wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)),
             basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
@@ -1544,7 +1546,8 @@ sub export_amf {
 sub _get_export_file {
     my ($self, $format) = @_;    
     my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml';
-    my $output_file = $self->{print}->output_filepath($main::opt{output} // '');
+    my $output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') };
+    Slic3r::GUI::catch_error($self) and return undef;
     $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/;
     my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
         basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm
index 1613b7e45..12ad2f12f 100644
--- a/lib/Slic3r/Print.pm
+++ b/lib/Slic3r/Print.pm
@@ -65,6 +65,9 @@ sub process {
 }
 
 # G-code export process, running at a background thread.
+# The export_gcode may die for various reasons (fails to process output_filename_format,
+# write error into the G-code, cannot execute post-processing scripts).
+# It is up to the caller to show an error message.
 sub export_gcode {
     my $self = shift;
     my %params = @_;
@@ -73,11 +76,12 @@ sub export_gcode {
     $self->process;
     
     # output everything to a G-code file
+    # The following call may die if the output_filename_format template substitution fails.
     my $output_file = $self->output_filepath($params{output_file} // '');
     $self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : ""));
 
-    die "G-code export to " . $output_file . " failed\n" 
-        if ! Slic3r::GCode->new->do_export($self, $output_file);
+    # The following line may die for multiple reasons.
+    Slic3r::GCode->new->do_export($self, $output_file);
     
     # run post-processing scripts
     if (@{$self->config->post_process}) {
@@ -99,6 +103,7 @@ sub export_gcode {
 }
 
 # Export SVG slices for the offline SLA printing.
+# The export_svg is expected to be executed inside an eval block.
 sub export_svg {
     my $self = shift;
     my %params = @_;
@@ -107,6 +112,7 @@ sub export_svg {
     
     my $fh = $params{output_fh};
     if (!$fh) {
+        # The following line may die if the output_filename_format template substitution fails.
         my $output_file = $self->output_filepath($params{output_file});
         $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/;
         Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
diff --git a/slic3r.pl b/slic3r.pl
index afddc77d3..c2bee2219 100755
--- a/slic3r.pl
+++ b/slic3r.pl
@@ -219,6 +219,8 @@ if (@ARGV) {  # slicing from command line
             $sprint->export_svg;
         } else {
             my $t0 = [gettimeofday];
+            # The following call may die if the output_filename_format template substitution fails,
+            # if the file cannot be written into, or if the post processing scripts cannot be executed.
             $sprint->export_gcode;
             
             # output some statistics
diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp
index bc3a86993..b5f186630 100644
--- a/xs/src/libslic3r/GCode.cpp
+++ b/xs/src/libslic3r/GCode.cpp
@@ -187,7 +187,7 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
         if (! start_filament_gcode.empty()) {
             // Process the start_filament_gcode for the active filament only.
             gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
-            gcode += gcodegen.placeholder_parser().process(start_filament_gcode, new_extruder_id);
+            gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id);
             check_add_eol(gcode);
         }
     }
@@ -362,7 +362,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
     return layers_to_print;
 }
 
-bool GCode::do_export(Print *print, const char *path)
+void GCode::do_export(Print *print, const char *path)
 {
     // Remove the old g-code if it exists.
     boost::nowide::remove(path);
@@ -372,23 +372,34 @@ bool GCode::do_export(Print *print, const char *path)
 
     FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb");
     if (file == nullptr)
-        return false;
+        throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
 
-    bool result = this->_do_export(*print, file);
+    this->m_placeholder_parser_failed_templates.clear();
+    this->_do_export(*print, file);
     fclose(file);
-
-    if (result && boost::nowide::rename(path_tmp.c_str(), path) != 0) {
-        boost::nowide::cerr << "Failed to remove the output G-code file from " << path_tmp << " to " << path
-            << ". Is " << path_tmp << " locked?" << std::endl;
-        result = false;
-    }
-
-    if (! result)
+    if (ferror(file)) {
         boost::nowide::remove(path_tmp.c_str());
-    return result;
+        throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
+    }
+    if (! this->m_placeholder_parser_failed_templates.empty()) {
+        // G-code export proceeded, but some of the PlaceholderParser substitutions failed.
+        std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n";
+        for (const std::string &name : this->m_placeholder_parser_failed_templates)
+            msg += std::string("\t") + name + "\n";
+        msg += "\nPlease inspect the file ";
+        msg += path_tmp + " for error messages enclosed between\n";
+        msg += "        !!!!! Failed to process the custom G-code template ...\n";
+        msg += "and\n";
+        msg += "        !!!!! End of an error report for the custom G-code template ...\n";
+        throw std::runtime_error(msg);
+    }
+    if (boost::nowide::rename(path_tmp.c_str(), path) != 0)
+        throw std::runtime_error(
+            std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
+            "Is " + path_tmp + " locked?" + '\n');
 }
 
-bool GCode::_do_export(Print &print, FILE *file)
+void GCode::_do_export(Print &print, FILE *file)
 {
     // How many times will be change_layer() called?
     // change_layer() in turn increments the progress bar status.
@@ -557,7 +568,7 @@ bool GCode::_do_export(Print &print, FILE *file)
     m_placeholder_parser.set("current_object_idx", 0);
     // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
     m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
-    std::string start_gcode = m_placeholder_parser.process(print.config.start_gcode.value, initial_extruder_id);
+    std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id);
     
     // Set bed temperature if the start G-code does not contain any bed temp control G-codes.
     this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
@@ -571,11 +582,11 @@ bool GCode::_do_export(Print &print, FILE *file)
             // Wipe tower will control the extruder switching, it will call the start_filament_gcode.
         } else {
             // Only initialize the initial extruder.
-            writeln(file, m_placeholder_parser.process(print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id));
+            writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id));
         }
     } else {
         for (const std::string &start_gcode : print.config.start_filament_gcode.values)
-            writeln(file, m_placeholder_parser.process(start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front())));
+            writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front())));
     }
     this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true);
     
@@ -668,7 +679,7 @@ bool GCode::_do_export(Print &print, FILE *file)
                     // another one, set first layer temperatures. This happens before the Z move
                     // is triggered, so machine has more time to reach such temperatures.
                     m_placeholder_parser.set("current_object_idx", int(finished_objects));
-                    std::string between_objects_gcode = m_placeholder_parser.process(print.config.between_objects_gcode.value, initial_extruder_id);
+                    std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config.between_objects_gcode.value, initial_extruder_id);
                     // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
                     this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
                     this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
@@ -747,12 +758,12 @@ bool GCode::_do_export(Print &print, FILE *file)
     // Process filament-specific gcode in extruder order.
     if (print.config.single_extruder_multi_material) {
         // Process the end_filament_gcode for the active filament only.
-        writeln(file, m_placeholder_parser.process(print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id()));
+        writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id()));
     } else {
         for (const std::string &end_gcode : print.config.end_filament_gcode.values)
-            writeln(file, m_placeholder_parser.process(end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front())));
+            writeln(file, this->placeholder_parser_process("end_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front())));
     }
-    writeln(file, m_placeholder_parser.process(print.config.end_gcode, m_writer.extruder()->id()));
+    writeln(file, this->placeholder_parser_process("end_gcode", print.config.end_gcode, m_writer.extruder()->id()));
     write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100%
     write(file, m_writer.postamble());
 
@@ -793,8 +804,21 @@ bool GCode::_do_export(Print &print, FILE *file)
                 fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
         }
     }
+}
 
-    return true;
+std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)
+{
+    try {
+        return m_placeholder_parser.process(templ, current_extruder_id, config_override);
+    } catch (std::runtime_error &err) {
+        // Collect the names of failed template substitutions for error reporting.
+        this->m_placeholder_parser_failed_templates.insert(name);
+        // Insert the macro error message into the G-code.
+        return 
+            std::string("!!!!! Failed to process the custom G-code template ") + name + "\n" + 
+            err.what() + 
+            "!!!!! End of an error report for the custom G-code template " + name + "\n";
+    }
 }
 
 // Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code.
@@ -997,7 +1021,7 @@ void GCode::process_layer(
         DynamicConfig config;
         config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
         config.set_key_value("layer_z",   new ConfigOptionFloat(print_z));
-        gcode += m_placeholder_parser.process(
+        gcode += this->placeholder_parser_process("before_layer_gcode",
             print.config.before_layer_gcode.value, m_writer.extruder()->id(), &config)
             + "\n";
     }
@@ -1007,7 +1031,7 @@ void GCode::process_layer(
         DynamicConfig config;
         config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
         config.set_key_value("layer_z",   new ConfigOptionFloat(print_z));
-        gcode += m_placeholder_parser.process(
+        gcode += this->placeholder_parser_process("layer_gcode",
             print.config.layer_gcode.value, m_writer.extruder()->id(), &config)
             + "\n";
     }
@@ -2206,7 +2230,7 @@ std::string GCode::set_extruder(unsigned int extruder_id)
         unsigned int        old_extruder_id     = m_writer.extruder()->id();
         const std::string  &end_filament_gcode  = m_config.end_filament_gcode.get_at(old_extruder_id);
         if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) {
-            gcode += m_placeholder_parser.process(end_filament_gcode, old_extruder_id);
+            gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
             check_add_eol(gcode);
         }
     }
@@ -2218,7 +2242,7 @@ std::string GCode::set_extruder(unsigned int extruder_id)
         DynamicConfig config;
         config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id()));
         config.set_key_value("next_extruder",     new ConfigOptionInt((int)extruder_id));
-        gcode += m_placeholder_parser.process(m_config.toolchange_gcode.value, extruder_id, &config);
+        gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config);
         check_add_eol(gcode);
     }
     
@@ -2232,7 +2256,7 @@ std::string GCode::set_extruder(unsigned int extruder_id)
     const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
     if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) {
         // Process the start_filament_gcode for the active filament only.
-        gcode += m_placeholder_parser.process(start_filament_gcode, extruder_id);
+        gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id);
         check_add_eol(gcode);
     }
     // Set the new extruder to the operating temperature.
diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp
index 72c39fe38..2fd3b39d3 100644
--- a/xs/src/libslic3r/GCode.hpp
+++ b/xs/src/libslic3r/GCode.hpp
@@ -130,7 +130,8 @@ public:
         {}
     ~GCode() {}
 
-    bool            do_export(Print *print, const char *path);
+    // throws std::runtime_exception
+    void            do_export(Print *print, const char *path);
 
     // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
     const Pointf&   origin() const { return m_origin; }
@@ -143,6 +144,9 @@ public:
     const Layer*    layer() const { return m_layer; }
     GCodeWriter&    writer() { return m_writer; }
     PlaceholderParser& placeholder_parser() { return m_placeholder_parser; }
+    // Process a template through the placeholder parser, collect error messages to be reported
+    // inside the generated string and after the G-code export finishes.
+    std::string     placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr);
     bool            enable_cooling_markers() const { return m_enable_cooling_markers; }
 
     // For Perl bindings, to be used exclusively by unit tests.
@@ -151,7 +155,7 @@ public:
     void            apply_print_config(const PrintConfig &print_config);
 
 protected:
-    bool            _do_export(Print &print, FILE *file);
+    void            _do_export(Print &print, FILE *file);
 
     // Object and support extrusions of the same PrintObject at the same print_z.
     struct LayerToPrint
@@ -223,6 +227,8 @@ protected:
     FullPrintConfig                     m_config;
     GCodeWriter                         m_writer;
     PlaceholderParser                   m_placeholder_parser;
+    // Collection of templates, on which the placeholder substitution failed.
+    std::set<std::string>               m_placeholder_parser_failed_templates;
     OozePrevention                      m_ooze_prevention;
     Wipe                                m_wipe;
     AvoidCrossingPerimeters             m_avoid_crossing_perimeters;
diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp
index b09e06133..f9f72a9e9 100644
--- a/xs/src/libslic3r/PlaceholderParser.cpp
+++ b/xs/src/libslic3r/PlaceholderParser.cpp
@@ -368,7 +368,7 @@ namespace client
                 value = lhs.to_string() == rhs.to_string();
             } else {
                 boost::throw_exception(qi::expectation_failure<Iterator>(
-                    lhs.it_range.begin(), rhs.it_range.end(), spirit::info("Cannot compare the types.")));
+                    lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
             }
             lhs.type = TYPE_BOOL;
             lhs.data.b = (op == '=') ? value : !value;
@@ -387,7 +387,7 @@ namespace client
         void throw_exception(const char *message) const 
         {
             boost::throw_exception(qi::expectation_failure<Iterator>(
-                this->it_range.begin(), this->it_range.end(), spirit::info(message)));
+                this->it_range.begin(), this->it_range.end(), spirit::info(std::string("*") + message)));
         }
 
         void throw_if_not_numeric(const char *message) const 
@@ -417,6 +417,7 @@ namespace client
         const PlaceholderParser *pp = nullptr;
         const DynamicConfig     *config_override = nullptr;
         const size_t             current_extruder_id = 0;
+        std::string              error_message;
 
         const ConfigOption*     resolve_symbol(const std::string &opt_key) const
         {
@@ -444,26 +445,22 @@ namespace client
                     opt = ctx->resolve_symbol(opt_key_str.substr(0, idx));
                     if (opt != nullptr) {
                         if (! opt->is_vector())
-                            boost::throw_exception(qi::expectation_failure<Iterator>(
-                                opt_key.begin(), opt_key.end(), spirit::info("Trying to index a scalar variable")));
+                            ctx->throw_exception("Trying to index a scalar variable", opt_key);
                         char *endptr = nullptr;
                         idx = strtol(opt_key_str.c_str() + idx + 1, &endptr, 10);
                         if (endptr == nullptr || *endptr != 0)
-                            boost::throw_exception(qi::expectation_failure<Iterator>(
-                                opt_key.begin() + idx + 1, opt_key.end(), spirit::info("Invalid vector index")));
+                            ctx->throw_exception("Invalid vector index", boost::iterator_range<Iterator>(opt_key.begin() + idx + 1, opt_key.end()));
                     }
                 }
             }
             if (opt == nullptr)
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt_key.begin(), opt_key.end(), spirit::info("Variable does not exist")));
+                ctx->throw_exception("Variable does not exist", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end()));
             if (opt->is_scalar())
                 output = opt->serialize();
             else {
                 const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt);
                 if (vec->empty())
-                    boost::throw_exception(qi::expectation_failure<Iterator>(
-                        opt_key.begin(), opt_key.end(), spirit::info("Indexing an empty vector variable")));
+                    ctx->throw_exception("Indexing an empty vector variable", opt_key);
                 output = vec->vserialize()[(idx >= vec->size()) ? 0 : idx];
             }
         }
@@ -484,23 +481,18 @@ namespace client
                 opt = ctx->resolve_symbol(opt_key_str);
             }
             if (! opt->is_vector())
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt_key.begin(), opt_key.end(), spirit::info("Trying to index a scalar variable")));
+                ctx->throw_exception("Trying to index a scalar variable", opt_key);
             const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt);
             if (vec->empty())
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt_key.begin(), opt_key.end(), spirit::info("Indexing an empty vector variable")));
+                ctx->throw_exception("Indexing an empty vector variable", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end()));
             const ConfigOption *opt_index = ctx->resolve_symbol(std::string(opt_vector_index.begin(), opt_vector_index.end()));
             if (opt_index == nullptr)
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt_key.begin(), opt_key.end(), spirit::info("Variable does not exist")));
+                ctx->throw_exception("Variable does not exist", opt_key);
             if (opt_index->type() != coInt)
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt_key.begin(), opt_key.end(), spirit::info("Indexing variable has to be integer")));
+                ctx->throw_exception("Indexing variable has to be integer", opt_key);
 			int idx = opt_index->getInt();
 			if (idx < 0)
-				boost::throw_exception(qi::expectation_failure<Iterator>(
-					opt_key.begin(), opt_key.end(), spirit::info("Negative vector index")));
+                ctx->throw_exception("Negative vector index", opt_key);
 			output = vec->vserialize()[(idx >= (int)vec->size()) ? 0 : idx];
         }
 
@@ -512,8 +504,7 @@ namespace client
         {
             const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end()));
             if (opt == nullptr)
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt_key.begin(), opt_key.end(), spirit::info("Not a variable name")));
+                ctx->throw_exception("Not a variable name", opt_key);
             output.opt = opt;
             output.it_range = opt_key;
         }
@@ -525,8 +516,7 @@ namespace client
             expr<Iterator>                  &output)
         {
             if (opt.opt->is_vector())
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt.it_range.begin(), opt.it_range.end(), spirit::info("Referencing a scalar variable in a vector context")));
+                ctx->throw_exception("Referencing a scalar variable in a vector context", opt.it_range);
             switch (opt.opt->type()) {
             case coFloat:   output.set_d(opt.opt->getFloat());   break;
             case coInt:     output.set_i(opt.opt->getInt());     break;
@@ -535,11 +525,9 @@ namespace client
             case coPoint:   output.set_s(opt.opt->serialize());  break;
             case coBool:    output.set_b(opt.opt->getBool());    break;
             case coFloatOrPercent:
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt.it_range.begin(), opt.it_range.end(), spirit::info("FloatOrPercent variables are not supported")));
+                ctx->throw_exception("FloatOrPercent variables are not supported", opt.it_range);
             default:
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt.it_range.begin(), opt.it_range.end(), spirit::info("Unknown scalar variable type")));
+                ctx->throw_exception("Unknown scalar variable type", opt.it_range);
             }
             output.it_range = opt.it_range;
         }
@@ -553,12 +541,10 @@ namespace client
             expr<Iterator>                  &output)
         {
             if (opt.opt->is_scalar())
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt.it_range.begin(), opt.it_range.end(), spirit::info("Referencing a vector variable in a scalar context")));
+                ctx->throw_exception("Referencing a vector variable in a scalar context", opt.it_range);
             const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt.opt);
             if (vec->empty())
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt.it_range.begin(), opt.it_range.end(), spirit::info("Indexing an empty vector variable")));
+                ctx->throw_exception("Indexing an empty vector variable", opt.it_range);
             size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index);
             switch (opt.opt->type()) {
             case coFloats:   output.set_d(static_cast<const ConfigOptionFloats  *>(opt.opt)->values[idx]); break;
@@ -568,8 +554,7 @@ namespace client
             case coPoints:   output.set_s(static_cast<const ConfigOptionPoints  *>(opt.opt)->values[idx].dump_perl()); break;
             case coBools:    output.set_b(static_cast<const ConfigOptionBools   *>(opt.opt)->values[idx] != 0); break;
             default:
-                boost::throw_exception(qi::expectation_failure<Iterator>(
-                    opt.it_range.begin(), opt.it_range.end(), spirit::info("Unknown vector variable type")));
+                ctx->throw_exception("Unknown vector variable type", opt.it_range);
             }
             output.it_range = boost::iterator_range<Iterator>(opt.it_range.begin(), it_end);
         }
@@ -579,10 +564,54 @@ namespace client
         template <typename Iterator>
         static void evaluate_index(expr<Iterator> &expr_index, int &output)
         {
-            if (expr_index.type != expr<Iterator>::TYPE_INT)
+            if (expr_index.type != expr<Iterator>::TYPE_INT)                
                 expr_index.throw_exception("Non-integer index is not allowed to address a vector variable.");
             output = expr_index.i();
         }
+
+        template <typename Iterator>
+        static void throw_exception(const std::string &msg, const boost::iterator_range<Iterator> &it_range)
+        {
+            // An asterix is added to the start of the string to differentiate the boost::spirit::info::tag content
+            // between the grammer terminal / non-terminal symbol name and a free-form error message.
+            boost::throw_exception(qi::expectation_failure<Iterator>(it_range.begin(), it_range.end(), spirit::info(std::string("*") + msg)));
+        }
+
+        template <typename Iterator>
+        static void process_error_message(const MyContext *context, const boost::spirit::info &info, const Iterator &it_begin, const Iterator &it_end)
+        {
+            struct expectation_printer
+            {
+                expectation_printer(std::string &msg) : result(msg) {}
+                std::string &result;
+                void element(std::string const& tag, std::string const& value, int depth)
+                {
+                    // Indent to depth.
+                    for (int i = 0; i < depth * 4; ++ i)
+                        result += ' ';
+                    if (tag.empty() || tag.front() != '*') {
+                        if (depth == 0)
+                            this->result += "Expecting ";
+                        this->result += "tag: ";
+                        this->result += tag;
+                    } else
+                        this->result += tag.substr(1);
+                    if (! value.empty()) {
+                        this->result += ", value: ";
+                        this->result += value;
+                    }
+                    this->result += '\n';
+                }
+            };
+
+            std::string &msg = const_cast<MyContext*>(context)->error_message;
+            msg += "Error! ";
+            expectation_printer ep(msg);
+            spirit::basic_info_walker<expectation_printer> walker(ep, info.tag, 0);
+            boost::apply_visitor(walker, info.value);
+            msg += " got: \"";
+            msg += std::string(it_begin, it_end) + "\"\n";
+        }
     };
 
     // For debugging the boost::spirit parsers. Print out the string enclosed in it_range.
@@ -651,7 +680,8 @@ namespace client
             first = it;
             return true;
         err:
-            boost::throw_exception(qi::expectation_failure<Iterator>(first, last, spirit::info("Invalid utf8 sequence")));
+            MyContext::throw_exception("Invalid utf8 sequence", boost::iterator_range<Iterator>(first, last));
+            return false;
         }
 
         // This function is called during error handling to create a human readable string for the error context.
@@ -692,6 +722,8 @@ namespace client
             qi::_val_type               _val;
             qi::_1_type                 _1;
             qi::_2_type                 _2;
+            qi::_3_type                 _3;
+            qi::_4_type                 _4;
             qi::_a_type                 _a;
             qi::_b_type                 _b;
             qi::_r1_type                _r1;
@@ -701,6 +733,7 @@ namespace client
             // Without it, some of the errors would not trigger the error handler.
             start = eps > text_block(_r1);
             start.name("start");
+            qi::on_error<qi::fail>(start, px::bind(&MyContext::process_error_message<Iterator>, _r1, _4, _3, _2));
 
             text_block = *(
                         text [_val+=_1]
@@ -831,16 +864,6 @@ namespace client
             variable_reference = identifier
                 [ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ];
             variable_reference.name("variable reference");
-/*
-            qi::on_error<qi::fail>(start, 
-                    phx::ref(std::cout)
-                       << "Error! Expecting "
-                       << qi::_4
-                       << " here: '"
-                       << px::construct<std::string>(qi::_3, qi::_2)
-                       << "'\n"
-                );
-*/
 
             keywords.add
                 ("and")
@@ -907,69 +930,30 @@ namespace client
     };
 }
 
-struct printer
-{
-    typedef spirit::utf8_string string;
-
-    void element(string const& tag, string const& value, int depth) const
-    {
-        for (int i = 0; i < (depth*4); ++i) // indent to depth
-            std::cout << ' ';
-        std::cout << "tag: " << tag;
-        if (value != "")
-            std::cout << ", value: " << value;
-        std::cout << std::endl;
-    }
-};
-
-void print_info(spirit::info const& what)
-{
-    using spirit::basic_info_walker;
-    printer pr;
-    basic_info_walker<printer> walker(pr, what.tag, 0);
-    boost::apply_visitor(walker, what.value);
-}
-
 std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const
 {
     typedef std::string::const_iterator iterator_type;
     typedef client::calculator<iterator_type> calculator;
 
-    spirit::ascii::space_type space; // Our skipper
-    calculator calc; // Our grammar
-
+    // Our whitespace skipper.
+    spirit::ascii::space_type   space;
+    // Our grammar.
+    calculator                  calc;
+    // Iterators over the source template.
     std::string::const_iterator iter = templ.begin();
-    std::string::const_iterator end = templ.end();
-    //std::string result;
-    std::string result;
-    bool r = false;
-    try {
-        client::MyContext context;
-        context.pp = this;
-        context.config_override = config_override;
-        r = phrase_parse(iter, end, calc(&context), space, result);
-    } catch (qi::expectation_failure<iterator_type> const& x) {
-        std::cout << "expected: "; print_info(x.what_);
-        std::cout << "got: \"" << std::string(x.first, x.last) << '"' << std::endl;
+    std::string::const_iterator end  = templ.end();
+    // Accumulator for the processed template.
+    std::string                 output;
+    client::MyContext context;
+    context.pp = this;
+    context.config_override = config_override;
+    bool res = phrase_parse(iter, end, calc(&context), space, output);
+    if (! context.error_message.empty()) {
+        if (context.error_message.back() != '\n' && context.error_message.back() != '\r')
+            context.error_message += '\n';
+        throw std::runtime_error(context.error_message);
     }
-
-    if (r && iter == end)
-    {
-//        std::cout << "-------------------------\n";
-//        std::cout << "Parsing succeeded\n";
-//        std::cout << "result = " << result << std::endl;
-//        std::cout << "-------------------------\n";
-    }
-    else
-    {
-        std::string rest(iter, end);
-        std::cout << "-------------------------\n";
-        std::cout << "Parsing failed\n";
-        std::cout << "stopped at: \" " << rest << "\"\n";
-        std::cout << "source: \n" << templ;       
-        std::cout << "-------------------------\n";
-    }
-    return result;
+    return output;
 }
 
 }
diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
index c25e5de7d..190fbfe6e 100644
--- a/xs/src/libslic3r/Print.cpp
+++ b/xs/src/libslic3r/Print.cpp
@@ -1075,7 +1075,11 @@ void Print::_make_wipe_tower()
 std::string Print::output_filename()
 {
     this->placeholder_parser.update_timestamp();
-    return this->placeholder_parser.process(this->config.output_filename_format.value, 0);
+    try {
+        return this->placeholder_parser.process(this->config.output_filename_format.value, 0);
+    } catch (std::runtime_error &err) {
+        throw std::runtime_error(std::string("Failed processing of the output_filename_format template.\n") + err.what());
+    }
 }
 
 std::string Print::output_filepath(const std::string &path)
diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp
index 92341394f..9e6df85dc 100644
--- a/xs/xsp/GCode.xsp
+++ b/xs/xsp/GCode.xsp
@@ -17,7 +17,14 @@
 %name{Slic3r::GCode} class GCode {
     GCode();
     ~GCode();
-    std::string do_export(Print *print, const char *path);
+    void do_export(Print *print, const char *path)
+        %code%{
+            try {
+                THIS->do_export(print, path);
+            } catch (std::exception& e) {
+                croak(e.what());
+            }
+        %};
 
     Ref<Pointf> origin()
         %code{% RETVAL = &(THIS->origin()); %};
diff --git a/xs/xsp/PlaceholderParser.xsp b/xs/xsp/PlaceholderParser.xsp
index 08630f9d5..ab06450f9 100644
--- a/xs/xsp/PlaceholderParser.xsp
+++ b/xs/xsp/PlaceholderParser.xsp
@@ -14,5 +14,11 @@
         %code%{ THIS->apply_config(*config); %};
     void set(std::string key, int value);
     std::string process(std::string str) const
-        %code%{ RETVAL = THIS->process(str, 0); %};
+        %code%{
+            try {
+                RETVAL = THIS->process(str, 0);
+            } catch (std::exception& e) {
+                croak(e.what());
+            }
+        %};
 };
diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
index bdf7b8991..1148fd4aa 100644
--- a/xs/xsp/Print.xsp
+++ b/xs/xsp/Print.xsp
@@ -206,8 +206,14 @@ _constant()
     double max_allowed_layer_height() const;
     bool has_support_material() const;
     void auto_assign_extruders(ModelObject* model_object);
-    std::string output_filename();
-    std::string output_filepath(std::string path = "");
+    std::string output_filepath(std::string path = "")
+        %code%{
+            try {
+                RETVAL = THIS->output_filepath(path);
+            } catch (std::exception& e) {
+                croak(e.what());
+            }
+        %};
     
     void add_model_object(ModelObject* model_object, int idx = -1);
     bool apply_config(DynamicPrintConfig* config)