diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/Sites.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/Sites.java
index c29b0bdf21b0e5665ca99915171f9dfa83bba560..a8ebc50d27b4a7182210440f37d3bb642ad3ab94 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/calc/Sites.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/calc/Sites.java
@@ -8,6 +8,7 @@ import java.io.IOException;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.Optional;
+import java.util.OptionalDouble;
 import java.util.Set;
 import java.util.stream.Stream;
 
@@ -38,21 +39,22 @@ public final class Sites {
   /**
    * Create an unmodifiable list of sites from the comma-delimted site file
    * designated by {@code path}. Site parameter values in a sites file take
-   * precedence over defaults from a config and model values in the supplied
-   * siteData.
+   * precedence over defaults and model values in the supplied siteData.
    *
    * @param path to comma-delimited site data file
+   * @param siteData site specific data from model
+   * @param vs30 optional vs30 value used when running multiple site classes
    * @throws IOException if a problem is encountered
    */
   public static List<Site> fromCsv(
       Path path,
-      CalcConfig config,
-      SiteData siteData) throws IOException {
+      SiteData siteData,
+      OptionalDouble vs30) throws IOException {
 
     DelimitedData dd = DelimitedData.comma(path);
     try (Stream<Record> stream = dd.records()) {
       return stream.map(record -> recordToSite(
-          record, dd.columnKeys(), config, siteData))
+          record, dd.columnKeys(), siteData, vs30))
           .collect(toUnmodifiableList());
     }
   }
@@ -60,14 +62,14 @@ public final class Sites {
   private static Site recordToSite(
       Record record,
       Set<String> keys,
-      CalcConfig config,
-      SiteData siteData) {
+      SiteData siteData,
+      OptionalDouble vs30) {
 
     /*
      * Site parameter precedence:
      *
      * (4) Defaults from Site.java, (3) site-data from model, (2) sites file,
-     * and (1) Vs30 from config
+     * and (1) Vs30 from config.vs30s[]
      *
      * If a sites file contains columns for basin depths, a 'null' value string
      * indicates a GMM should do whatever it defines as default behavior and
@@ -82,7 +84,7 @@ public final class Sites {
         record.getDouble(Site.Key.LON),
         record.getDouble(Site.Key.LAT));
 
-    Site.Builder builder = initBuilder(loc, config, siteData);
+    Site.Builder builder = initBuilder(loc, siteData);
 
     // Override with values from sites file
     if (keys.contains(Site.Key.NAME)) {
@@ -101,16 +103,18 @@ public final class Sites {
       builder.z2p5(record.getDouble(Site.Key.Z2P5));
     }
 
+    // Override with site class batch value
+    vs30.ifPresent(builder::vs30);
+
     return builder.build();
   }
 
   /*
-   * Initialize a builder with location, config defaults and possible site
-   * specific values for basin and other terms.
+   * Initialize a builder with location and possible site specific values for
+   * basin and other terms from the model being run.
    */
   private static Site.Builder initBuilder(
       Location loc,
-      CalcConfig config,
       SiteData siteData) {
 
     Site.Builder builder = Site.builder().location(loc);
@@ -127,54 +131,56 @@ public final class Sites {
    * features or a polygon feature and optional bounds feature.
    *
    * <p>A GeoJSON of points may include site parameters as properties of each
-   * feature; these will override any default values from a config and model
-   * values in the supplied siteData. Put any default values for use with
-   * GeoJSON map polygon in the supplied {@code config}.
+   * feature; these will override default values and model values in the
+   * supplied siteData.
    *
    * @param path to GeoJson site data file
+   * @param siteData site specific data from model
+   * @param vs30 optional vs30 value used when running multiple site classes
    * @throws IOException if a problem is encountered
    */
   public static List<Site> fromGeoJson(
       Path path,
-      CalcConfig defaults,
-      SiteData siteData) throws IOException {
+      SiteData siteData,
+      OptionalDouble vs30) throws IOException {
 
     FeatureCollection fc = GeoJson.from(path).toFeatureCollection();
     List<Feature> features = fc.features();
 
     if (features.get(0).type() == Type.POINT) {
-      return fromPoints(features, defaults, siteData);
+      return fromPoints(features, siteData, vs30);
     } else {
-      return fromPolygon(features, defaults, siteData);
+      return fromPolygon(features, siteData, vs30);
     }
   }
 
   private static List<Site> fromPoints(
       List<Feature> features,
-      CalcConfig defaults,
-      SiteData siteData) {
+      SiteData siteData,
+      OptionalDouble vs30) {
 
     return features.stream()
-        .map(feature -> fromPoint(feature, defaults, siteData))
+        .map(feature -> fromPoint(feature, siteData, vs30))
         .collect(toUnmodifiableList());
   }
 
-  private static Site fromPoint(Feature feature, CalcConfig config, SiteData siteData) {
+  private static Site fromPoint(Feature feature, SiteData siteData, OptionalDouble vs30) {
     Location loc = feature.asPoint();
-    Site.Builder builder = initBuilder(loc, config, siteData);
+    Site.Builder builder = initBuilder(loc, siteData);
     Properties props = feature.properties();
     props.getString(Site.Key.NAME).ifPresent(builder::name);
     props.getDouble(Site.Key.VS30).ifPresent(builder::vs30);
     props.getBoolean(Site.Key.VS_INF).ifPresent(builder::vsInferred);
     props.getDouble(Site.Key.Z1P0).ifPresent(builder::z1p0);
     props.getDouble(Site.Key.Z2P5).ifPresent(builder::z2p5);
+    vs30.ifPresent(builder::vs30);
     return builder.build();
   }
 
   private static List<Site> fromPolygon(
       List<Feature> features,
-      CalcConfig config,
-      SiteData siteData) {
+      SiteData siteData,
+      OptionalDouble vs30) {
 
     Optional<Bounds> mapBounds = Optional.empty();
     String boundsName = "";
@@ -222,11 +228,20 @@ public final class Sites {
         GriddedRegion.ANCHOR_0_0);
 
     return region.nodes().stream()
-        .map(loc -> initBuilder(loc, config, siteData))
-        .map(Site.Builder::build)
+        .map(loc -> locationToSite(loc, siteData, vs30))
         .collect(toUnmodifiableList());
   }
 
+  private static Site locationToSite(
+      Location loc,
+      SiteData siteData,
+      OptionalDouble vs30) {
+
+    Site.Builder site = initBuilder(loc, siteData);
+    vs30.ifPresent(site::vs30);
+    return site.build();
+  }
+
   private static final int TO_STRING_LIMIT = 5;
   private static final String SITE_INDENT = LOG_INDENT + "       ";
 
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/calc/SitesTests.java b/src/test/java/gov/usgs/earthquake/nshmp/calc/SitesTests.java
index 4e07b558c5af57a9da18f25c0278b58a8814f2ad..e30b36cdf7f4222140d3468fc0955c5cf598c6ca 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/calc/SitesTests.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/calc/SitesTests.java
@@ -6,6 +6,7 @@ import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.OptionalDouble;
 
 import org.junit.jupiter.api.Test;
 
@@ -21,12 +22,13 @@ class SitesTests {
 
   @Test
   final void testCsv() throws IOException {
-    CalcConfig config = CalcConfig.defaults().build();
 
-    List<Site> sites = Sites.fromCsv(CSV_PATH, config, SiteData.EMPTY);
+    List<Site> sites = Sites.fromCsv(
+        CSV_PATH, SiteData.EMPTY, OptionalDouble.empty());
     assertEquals(6, sites.size());
 
-    List<Site> sitesNoProps = Sites.fromCsv(CSV_NO_PROPS_PATH, config, SiteData.EMPTY);
+    List<Site> sitesNoProps = Sites.fromCsv(
+        CSV_NO_PROPS_PATH, SiteData.EMPTY, OptionalDouble.empty());
     assertEquals(6, sitesNoProps.size());
 
     // toString
@@ -35,12 +37,13 @@ class SitesTests {
 
   @Test
   final void testGeoJson() throws IOException {
-    CalcConfig config = CalcConfig.defaults().build();
 
-    List<Site> sitesPoints = Sites.fromGeoJson(GEOJSON_POINTS_PATH, config, SiteData.EMPTY);
+    List<Site> sitesPoints = Sites.fromGeoJson(
+        GEOJSON_POINTS_PATH, SiteData.EMPTY, OptionalDouble.empty());
     assertEquals(3, sitesPoints.size());
 
-    List<Site> sitesPolygon = Sites.fromGeoJson(GEOJSON_POLYGON_PATH, config, SiteData.EMPTY);
+    List<Site> sitesPolygon = Sites.fromGeoJson(
+        GEOJSON_POLYGON_PATH, SiteData.EMPTY, OptionalDouble.empty());
     assertEquals(11654, sitesPolygon.size());
   }
 
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/model/LoaderTests.java b/src/test/java/gov/usgs/earthquake/nshmp/model/LoaderTests.java
index c1b56bb6487e40ceab0b22ec2a9e981435930133..f065ddf6c99932c4393fbdca38520ef1f6f12691 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/model/LoaderTests.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/model/LoaderTests.java
@@ -15,6 +15,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.OptionalDouble;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.stream.Stream;
@@ -59,7 +60,7 @@ class LoaderTests {
   @BeforeAll
   static void setUpBeforeClass() throws IOException {
     model = ModelLoader.load(MODEL_PATH);
-    sites = Sites.fromCsv(SITES_PATH, model.config(), model.siteData());
+    sites = Sites.fromCsv(SITES_PATH, model.siteData(), OptionalDouble.empty());
     expecteds = loadExpecteds();
     exec = initExecutor(model.config().performance.threadCount);
   }
@@ -202,7 +203,7 @@ class LoaderTests {
 
   public static void main(String[] args) throws IOException {
     model = ModelLoader.load(MODEL_PATH);
-    List<Site> sites = Sites.fromCsv(SITES_PATH, model.config(), model.siteData());
+    List<Site> sites = Sites.fromCsv(SITES_PATH, model.siteData(), OptionalDouble.empty());
     calcWrite(model, model.config(), sites);
   }