diff --git a/OSX/Metabase/UI/MainViewController.m b/OSX/Metabase/UI/MainViewController.m
index 91c7aeb5dff4d1111b82e892d268d32d0b9bb2c7..14263b876daff3ee92ebcaa5cfbfd273204f8889 100644
--- a/OSX/Metabase/UI/MainViewController.m
+++ b/OSX/Metabase/UI/MainViewController.m
@@ -115,33 +115,54 @@ NSString *BaseURL() {
 	[self.webView.mainFrame loadRequest:request];
 }
 
-- (void)saveCSV:(NSString *)datasetQuery {
+- (void)downloadWithMethod:(NSString *)methodString url:(NSString *)urlString params:(NSDictionary *)paramsDict extensions:(NSArray *)extensions {
 	NSSavePanel *savePanel			= [NSSavePanel savePanel];
-	savePanel.allowedFileTypes		= @[@"csv"];
-	savePanel.allowsOtherFileTypes	= NO;
 	savePanel.extensionHidden		= NO;
 	savePanel.showsTagField			= NO;
+    if ([extensions count] > 0) {
+        savePanel.allowedFileTypes = extensions;
+        savePanel.allowsOtherFileTypes = NO;
+    }
 	
 	NSString *downloadsDirectory	=  NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, YES)[0];
 	savePanel.directoryURL			= [NSURL URLWithString:downloadsDirectory];
-	
+    
+    // TODO: either figure out how to pull default filename from the Content-Disposition header or pass it in from JS land.
 	NSDateFormatter *dateFormatter	= [[NSDateFormatter alloc] init];
 	dateFormatter.locale			= [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
 	dateFormatter.dateFormat		= @"yyyy-MM-dd'T'HH_mm_ss";
 	savePanel.nameFieldStringValue	= [NSString stringWithFormat:@"query_result_%@", [dateFormatter stringFromDate:[NSDate date]]];
 	
 	if ([savePanel runModal] == NSFileHandlingPanelOKButton) {
-		NSLog(@"Will save CSV at: %@", savePanel.URL);
+		NSLog(@"Will save file at: %@", savePanel.URL);
 		
-		NSURL *url = [NSURL URLWithString:@"/api/dataset/csv" relativeToURL:[NSURL URLWithString:BaseURL()]];
-        NSString *postBody = [@"query=" stringByAppendingString:datasetQuery];
-        NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding];
-		NSMutableURLRequest *csvRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0f];
-        csvRequest.HTTPMethod = @"POST";
-        [csvRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
-        [csvRequest setValue:[NSString stringWithFormat:@"%lu", (NSUInteger)data.length] forHTTPHeaderField:@"Content-Length"];
-        csvRequest.HTTPBody = data;
-		[NSURLConnection sendAsynchronousRequest:csvRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
+        NSString *method = [methodString uppercaseString];
+        
+		NSURL *url = [NSURL URLWithString:urlString relativeToURL:[NSURL URLWithString:BaseURL()]];
+
+        NSMutableString *query = [NSMutableString string];
+        for (NSString* key in paramsDict) {
+            NSString* value = [paramsDict objectForKey:key];
+            [query appendFormat:@"%@=%@",
+                                [key stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]],
+                                [value stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
+        }
+        
+        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0f];
+        request.HTTPMethod = [method uppercaseString];
+        
+        if ([query length] > 0) {
+            if ([request.HTTPMethod isEqualToString:@"POST"] || [request.HTTPMethod isEqualToString:@"PUT"]) {
+                NSData *data = [query dataUsingEncoding:NSUTF8StringEncoding];
+                [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
+                [request setValue:[NSString stringWithFormat:@"%lu", (NSUInteger)data.length] forHTTPHeaderField:@"Content-Length"];
+                request.HTTPBody = data;
+            } else {
+                [request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", urlString, query] relativeToURL:[NSURL URLWithString:BaseURL()]]];
+            }
+        }
+
+		[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
 			NSError *writeError = nil;
 			[data writeToURL:savePanel.URL options:NSDataWritingAtomic error:&writeError];
 			
@@ -165,11 +186,11 @@ NSString *BaseURL() {
 	};
 	
 	// custom functions for OS X integration are available to the frontend as properties of window.OSX
-	context[@"OSX"] = @{@"saveCSV": ^(JSValue *datasetQuery){
-		[self saveCSV:datasetQuery.description];
-	}, @"resetPassword": ^(){
+    context[@"OSX"] = @{@"download": ^(JSValue *method, JSValue *url, JSValue *params, JSValue *extensions) {
+        [self downloadWithMethod:[method toString] url:[url toString] params:[params toDictionary] extensions:[extensions toArray]];
+    }, @"resetPassword": ^(){
 		[self resetPassword:nil];
-	}};
+    }};
 }
 
 
diff --git a/frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx b/frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx
index 2913ed122cd7f4507ad3266258be4a5999b01b04..daa7e5f96e711a12641f1404f98c2c8a52408b9f 100644
--- a/frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/MetricForm.jsx
@@ -4,7 +4,7 @@ import { Link } from "react-router";
 import FormLabel from "../components/FormLabel.jsx";
 import FormInput from "../components/FormInput.jsx";
 import FormTextArea from "../components/FormTextArea.jsx";
-import FieldSet from "../components/FieldSet.jsx";
+import FieldSet from "metabase/components/FieldSet.jsx";
 import PartialQueryBuilder from "../components/PartialQueryBuilder.jsx";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx";
 
diff --git a/frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx b/frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx
index 09f324407fa8632c99e1fe551243877327958827..f6b10dc6fdd91a127eb314e220f77c417750ab3c 100644
--- a/frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/SegmentForm.jsx
@@ -4,7 +4,7 @@ import { Link } from "react-router";
 import FormLabel from "../components/FormLabel.jsx";
 import FormInput from "../components/FormInput.jsx";
 import FormTextArea from "../components/FormTextArea.jsx";
-import FieldSet from "../components/FieldSet.jsx";
+import FieldSet from "metabase/components/FieldSet.jsx";
 import PartialQueryBuilder from "../components/PartialQueryBuilder.jsx";
 import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper.jsx";
 
diff --git a/frontend/src/metabase/components/Button.jsx b/frontend/src/metabase/components/Button.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..8ce1b897e0fd8ceb120c0a765eb6d1a7e7d11980
--- /dev/null
+++ b/frontend/src/metabase/components/Button.jsx
@@ -0,0 +1,52 @@
+import React, { Component, PropTypes } from "react";
+
+import Icon from "metabase/components/Icon.jsx";
+
+import cx from "classnames";
+
+import _ from "underscore";
+
+const BUTTON_VARIANTS = [
+    "small",
+    "medium",
+    "large",
+    "primary",
+    "warning",
+    "cancel",
+    "purple",
+    "borderless"
+];
+
+const Button = ({ className, icon, children, ...props }) => {
+    let variantClasses = BUTTON_VARIANTS.filter(variant => props[variant]).map(variant => "Button--" + variant);
+    return (
+        <button
+            {..._.omit(props, ...variantClasses)}
+            className={cx("Button", className, variantClasses)}
+        >
+            <div className="flex layout-centered">
+                { icon && <Icon name={icon} size={14} className="mr1" />}
+                <div>{children}</div>
+            </div>
+        </button>
+    );
+}
+
+Button.propTypes = {
+    className: PropTypes.string,
+    icon: PropTypes.string,
+    children: PropTypes.any,
+
+    small: PropTypes.bool,
+    medium: PropTypes.bool,
+    large: PropTypes.bool,
+
+    primary: PropTypes.bool,
+    warning: PropTypes.bool,
+    cancel: PropTypes.bool,
+    purple: PropTypes.bool,
+
+    borderless: PropTypes.bool
+};
+
+export default Button;
diff --git a/frontend/src/metabase/components/DownloadButton.jsx b/frontend/src/metabase/components/DownloadButton.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..003f86365b2dfb90d8328cd7ea40078ee118f94a
--- /dev/null
+++ b/frontend/src/metabase/components/DownloadButton.jsx
@@ -0,0 +1,43 @@
+import React, { Component, PropTypes } from "react";
+import ReactDOM from "react-dom";
+
+import Button from "metabase/components/Button.jsx";
+
+const DownloadButton = ({ className, style, children, method, url, params, extensions, ...props }) =>
+    <form className={className} style={style} method={method} action={url}>
+        { Object.entries(params).map(([name, value]) =>
+            <input key={name} type="hidden" name={name} value={value} />
+        )}
+        <Button
+            onClick={(e) => {
+                if (window.OSX) {
+                    // prevent form from being submitted normally
+                    e.preventDefault();
+                    // download using the API provided by the OS X app
+                    window.OSX.download(method, url, params, extensions);
+                }
+            }}
+            {...props}
+        >
+            {children}
+        </Button>
+    </form>
+
+DownloadButton.propTypes = {
+    className: PropTypes.string,
+    style: PropTypes.object,
+    url: PropTypes.string.isRequired,
+    method: PropTypes.string,
+    params: PropTypes.object,
+    icon: PropTypes.string,
+    extensions: PropTypes.array,
+};
+
+DownloadButton.defaultProps = {
+    icon: "downarrow",
+    method: "POST",
+    params: {},
+    extensions: []
+};
+
+export default DownloadButton;
diff --git a/frontend/src/metabase/admin/datamodel/components/FieldSet.jsx b/frontend/src/metabase/components/FieldSet.jsx
similarity index 66%
rename from frontend/src/metabase/admin/datamodel/components/FieldSet.jsx
rename to frontend/src/metabase/components/FieldSet.jsx
index 916baab703e79e38dbe9ca374820f776b281bd95..d303b0ece1ab0b7d9423d6d10df9707071a4492f 100644
--- a/frontend/src/metabase/admin/datamodel/components/FieldSet.jsx
+++ b/frontend/src/metabase/components/FieldSet.jsx
@@ -1,15 +1,17 @@
 import React, { Component, PropTypes } from "react";
 
+import cx from "classnames";
+
 export default class FieldSet extends Component {
     static propTypes = {};
     static defaultProps = {
-        border: "border-brand"
+        className: "border-brand"
     };
 
     render() {
-        const { children, legend, border } = this.props;
+        const { className, children, legend } = this.props;
         return (
-            <fieldset className={"px2 pb2 bordered rounded " + border}>
+            <fieldset className={cx(className, "px2 pb2 bordered rounded")}>
                 {legend && <legend className="h5 text-bold text-uppercase px1" style={{ marginLeft: "-0.5rem" }}>{legend}</legend>}
                 {children}
             </fieldset>
diff --git a/frontend/src/metabase/icon_paths.js b/frontend/src/metabase/icon_paths.js
index 8cdc18d482994f1abf53f5d9077450b17e1d5613..609209a7b821411e7a40815677aecd5fec59e3cc 100644
--- a/frontend/src/metabase/icon_paths.js
+++ b/frontend/src/metabase/icon_paths.js
@@ -56,6 +56,7 @@ export var ICON_PATHS = {
     dashboard: 'M32,29 L32,4 L32,0 L0,0 L0,8 L28,8 L28,28 L4,28 L4,8 L0,8 L0,29.5 L0,32 L32,32 L32,29 Z M7.27272727,18.9090909 L17.4545455,18.9090909 L17.4545455,23.2727273 L7.27272727,23.2727273 L7.27272727,18.9090909 Z M7.27272727,12.0909091 L24.7272727,12.0909091 L24.7272727,16.4545455 L7.27272727,16.4545455 L7.27272727,12.0909091 Z M20.3636364,18.9090909 L24.7272727,18.9090909 L24.7272727,23.2727273 L20.3636364,23.2727273 L20.3636364,18.9090909 Z',
     dashboards: 'M17,5.49100518 L17,10.5089948 C17,10.7801695 17.2276528,11 17.5096495,11 L26.4903505,11 C26.7718221,11 27,10.7721195 27,10.5089948 L27,5.49100518 C27,5.21983051 26.7723472,5 26.4903505,5 L17.5096495,5 C17.2281779,5 17,5.22788048 17,5.49100518 Z M18.5017326,14 C18.225722,14 18,13.77328 18,13.4982674 L18,26.5017326 C18,26.225722 18.22672,26 18.5017326,26 L5.49826741,26 C5.77427798,26 6,26.22672 6,26.5017326 L6,13.4982674 C6,13.774278 5.77327997,14 5.49826741,14 L18.5017326,14 Z M14.4903505,6 C14.2278953,6 14,5.78028538 14,5.49100518 L14,10.5089948 C14,10.2167107 14.2224208,10 14.4903505,10 L5.50964952,10 C5.77210473,10 6,10.2197146 6,10.5089948 L6,5.49100518 C6,5.78328929 5.77757924,6 5.50964952,6 L14.4903505,6 Z M26.5089948,22 C26.2251201,22 26,21.7774008 26,21.4910052 L26,26.5089948 C26,26.2251201 26.2225992,26 26.5089948,26 L21.4910052,26 C21.7748799,26 22,26.2225992 22,26.5089948 L22,21.4910052 C22,21.7748799 21.7774008,22 21.4910052,22 L26.5089948,22 Z M26.5089948,14 C26.2251201,14 26,13.7774008 26,13.4910052 L26,18.5089948 C26,18.2251201 26.2225992,18 26.5089948,18 L21.4910052,18 C21.7748799,18 22,18.2225992 22,18.5089948 L22,13.4910052 C22,13.7748799 21.7774008,14 21.4910052,14 L26.5089948,14 Z M26.4903505,6 C26.2278953,6 26,5.78028538 26,5.49100518 L26,10.5089948 C26,10.2167107 26.2224208,10 26.4903505,10 L17.5096495,10 C17.7721047,10 18,10.2197146 18,10.5089948 L18,5.49100518 C18,5.78328929 17.7775792,6 17.5096495,6 L26.4903505,6 Z M5,13.4982674 L5,26.5017326 C5,26.7769181 5.21990657,27 5.49826741,27 L18.5017326,27 C18.7769181,27 19,26.7800934 19,26.5017326 L19,13.4982674 C19,13.2230819 18.7800934,13 18.5017326,13 L5.49826741,13 C5.22308192,13 5,13.2199066 5,13.4982674 Z M5,5.49100518 L5,10.5089948 C5,10.7801695 5.22765279,11 5.50964952,11 L14.4903505,11 C14.7718221,11 15,10.7721195 15,10.5089948 L15,5.49100518 C15,5.21983051 14.7723472,5 14.4903505,5 L5.50964952,5 C5.22817786,5 5,5.22788048 5,5.49100518 Z M21,21.4910052 L21,26.5089948 C21,26.7801695 21.2278805,27 21.4910052,27 L26.5089948,27 C26.7801695,27 27,26.7721195 27,26.5089948 L27,21.4910052 C27,21.2198305 26.7721195,21 26.5089948,21 L21.4910052,21 C21.2198305,21 21,21.2278805 21,21.4910052 Z M21,13.4910052 L21,18.5089948 C21,18.7801695 21.2278805,19 21.4910052,19 L26.5089948,19 C26.7801695,19 27,18.7721195 27,18.5089948 L27,13.4910052 C27,13.2198305 26.7721195,13 26.5089948,13 L21.4910052,13 C21.2198305,13 21,13.2278805 21,13.4910052 Z',
     document: 'M29,10.1052632 L29,28.8325291 C29,30.581875 27.5842615,32 25.8337327,32 L7.16626728,32 C5.41758615,32 4,30.5837102 4,28.8441405 L4,3.15585953 C4,1.41292644 5.42339685,9.39605581e-15 7.15970573,8.42009882e-15 L20.713352,8.01767853e-16 L20.713352,8.42105263 L22.3846872,8.42105263 L22.3846872,0.310375032 L28.7849894,8.42105263 L20.713352,8.42105263 L20.713352,10.1052632 L29,10.1052632 Z M7.3426704,12.8000006 L25.7273576,12.8000006 L25.7273576,14.4842112 L7.3426704,14.4842112 L7.3426704,12.8000006 Z M7.3426704,17.3473687 L25.7273576,17.3473687 L25.7273576,19.0315793 L7.3426704,19.0315793 L7.3426704,17.3473687 Z M7.3426704,21.8947352 L25.7273576,21.8947352 L25.7273576,23.5789458 L7.3426704,23.5789458 L7.3426704,21.8947352 Z M7.43137255,26.2736849 L16.535014,26.2736849 L16.535014,27.9578954 L7.43137255,27.9578954 L7.43137255,26.2736849 Z',
+    downarrow: 'M12.2782161,19.3207547 L12.2782161,0 L19.5564322,0 L19.5564322,19.3207547 L26.8346484,19.3207547 L15.9173242,32 L5,19.3207547 L12.2782161,19.3207547 Z',
     download: {
         path: 'M4,8 L4,0 L7,0 L7,8 L10,8 L5.5,13.25 L1,8 L4,8 Z M11,14 L0,14 L0,17 L11,17 L11,14 Z',
         attrs: { viewBox: '0 0 11 17' }
diff --git a/frontend/src/metabase/query_builder/components/DownloadWidget.jsx b/frontend/src/metabase/query_builder/components/DownloadWidget.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..bf0fb87cb2a7c908bceb13284d691251c69c6039
--- /dev/null
+++ b/frontend/src/metabase/query_builder/components/DownloadWidget.jsx
@@ -0,0 +1,50 @@
+import React, { Component, PropTypes } from "react";
+import ReactDOM from "react-dom";
+
+import PopoverWithTrigger from "metabase/components/PopoverWithTrigger.jsx";
+import Icon from "metabase/components/Icon.jsx";
+import DownloadButton from "metabase/components/DownloadButton.jsx";
+
+import FieldSet from "metabase/components/FieldSet.jsx";
+
+const DownloadWidget = ({ className, card, datasetQuery, isLarge }) =>
+    <PopoverWithTrigger
+        triggerElement={<Icon className={className} title="Download this data" name='download' size={16} />}
+    >
+        <div className="p2" style={{ maxWidth: 300 }}>
+            <h4>Download</h4>
+            {isLarge &&
+                <FieldSet className="my2 text-gold border-gold" legend="Warning">
+                    <div className="my1">Your answer has a large number of rows so it could take awhile to download.</div>
+                    <div>The maximum download size is 1 million rows.</div>
+                </FieldSet>
+            }
+            <div className="flex flex-row mt2">
+                {["csv", "json"].map(type =>
+                    <DownloadButton
+                        className="mr1 text-uppercase text-default"
+                        url={card.id != null ?
+                            `/api/card/${card.id}/query/${type}`:
+                            `/api/dataset/${type}`
+                        }
+                        params={card.id != null ?
+                            { parameters: JSON.stringify(datasetQuery.parameters) } :
+                            { query: JSON.stringify(datasetQuery) }
+                        }
+                        extensions={[type]}
+                    >
+                        {type}
+                    </DownloadButton>
+                )}
+            </div>
+        </div>
+    </PopoverWithTrigger>
+
+DownloadWidget.propTypes = {
+    className: PropTypes.string,
+    card: PropTypes.object.isRequired,
+    datasetQuery: PropTypes.object.isRequired,
+    isLarge: PropTypes.bool
+};
+
+export default DownloadWidget;
diff --git a/frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx b/frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx
index 34a66a47c2e7b6b5a14c4a19ce02a09171103fdc..c34fbc9fcc6828d53b580e3d02c7aa8a5152b052 100644
--- a/frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx
+++ b/frontend/src/metabase/query_builder/components/QueryDefinitionTooltip.jsx
@@ -2,7 +2,7 @@ import React, { Component, PropTypes } from "react";
 
 import FilterList from "./filters/FilterList.jsx";
 import AggregationWidget from "./AggregationWidget.jsx";
-import FieldSet from "metabase/admin/datamodel/components/FieldSet.jsx";
+import FieldSet from "metabase/components/FieldSet.jsx";
 
 import Query from "metabase/lib/query";
 
@@ -29,7 +29,7 @@ export default class QueryDefinitionTooltip extends Component {
                 </div>
                 { object.definition &&
                     <div className="mt2">
-                        <FieldSet legend="Definition" border="border-light">
+                        <FieldSet legend="Definition" className="border-light">
                             <div className="TooltipFilterList">
                                 { object.definition.aggregation &&
                                     <AggregationWidget
diff --git a/frontend/src/metabase/query_builder/components/QueryVisualization.jsx b/frontend/src/metabase/query_builder/components/QueryVisualization.jsx
index 4defd873bed8e38b216acb41f04e0d083b8a40cd..adfac0aad64e20422a2e238ec4767f43ad47ec60 100644
--- a/frontend/src/metabase/query_builder/components/QueryVisualization.jsx
+++ b/frontend/src/metabase/query_builder/components/QueryVisualization.jsx
@@ -2,15 +2,13 @@ import React, { Component, PropTypes } from "react";
 import ReactDOM from "react-dom";
 import { Link } from "react-router";
 
-import Icon from "metabase/components/Icon.jsx";
 import LoadingSpinner from 'metabase/components/LoadingSpinner.jsx';
 import RunButton from './RunButton.jsx';
 import VisualizationSettings from './VisualizationSettings.jsx';
 
 import VisualizationError from "./VisualizationError.jsx";
 import VisualizationResult from "./VisualizationResult.jsx";
-
-import ModalWithTrigger from "metabase/components/ModalWithTrigger.jsx";
+import DownloadWidget from "./DownloadWidget.jsx";
 
 import cx from "classnames";
 import _ from "underscore";
@@ -80,7 +78,7 @@ export default class QueryVisualization extends Component {
     }
 
     renderHeader() {
-        const { isObjectDetail, isRunning } = this.props;
+        const { isObjectDetail, isRunning, card, result } = this.props;
         return (
             <div className="relative flex flex-no-shrink mt3 mb1" style={{ minHeight: "2em" }}>
                 <span className="relative z4">
@@ -96,8 +94,15 @@ export default class QueryVisualization extends Component {
                     />
                 </div>
                 <div className="absolute right z4 flex align-center">
-                    {!this.queryIsDirty() && this.renderCount()}
-                    {this.renderDownloadButton()}
+                    { !this.queryIsDirty() && this.renderCount() }
+                    { !this.queryIsDirty() && result && !result.error ?
+                        <DownloadWidget
+                            className="mx1"
+                            card={card}
+                            datasetQuery={result.json_query}
+                            isLarge={result.data.rows_truncated != null}
+                        />
+                    : null }
                 </div>
             </div>
         );
@@ -131,81 +136,6 @@ export default class QueryVisualization extends Component {
         }
     }
 
-    onDownloadCSV() {
-        const form = ReactDOM.findDOMNode(this._downloadCsvForm);
-        form.query.value = JSON.stringify(this.props.fullDatasetQuery);
-        form.submit();
-    }
-
-    renderDownloadButton() {
-        const { card, result } = this.props;
-
-        const csvUrl = card.id != null ? `/api/card/${card.id}/query/csv`: "/api/dataset/csv";
-
-        if (result && !result.error) {
-            if (result && result.data && result.data.rows_truncated) {
-                // this is a "large" dataset, so show a modal to inform users about this and make them click again to d/l
-                let downloadButton;
-                if (window.OSX) {
-                    downloadButton = (<button className="Button Button--primary" onClick={() => {
-                            window.OSX.saveCSV(JSON.stringify(card.dataset_query));
-                            this.refs.downloadModal.toggle()
-                        }}>Download CSV</button>);
-                } else {
-                    downloadButton = (
-                        <form ref={(c) => this._downloadCsvForm = c} method="POST" action={csvUrl}>
-                            <input type="hidden" name="query" value="" />
-                            <a className="Button Button--primary" onClick={() => {this.onDownloadCSV(); this.refs.downloadModal.toggle();}}>
-                                Download CSV
-                            </a>
-                        </form>
-                    );
-                }
-
-                return (
-                    <ModalWithTrigger
-                        key="download"
-                        ref="downloadModal"
-                        className="Modal Modal--small"
-                        triggerElement={<Icon className="mx1" title="Download this data" name='download' size={16} />}
-                    >
-                        <div style={{width: "480px"}} className="Modal--small p4 text-centered relative">
-                            <span className="absolute top right p4 text-normal text-grey-3 cursor-pointer" onClick={() => this.refs.downloadModal.toggle()}>
-                                <Icon name={'close'} size={16} />
-                            </span>
-                            <div className="p3 text-strong">
-                                <h2 className="text-bold">Download large data set</h2>
-                                <div className="pt2">Your answer has a large amount of data so we wanted to let you know it could take a while to download.</div>
-                                <div className="py4">The maximum download amount is 1 million rows.</div>
-                                {downloadButton}
-                            </div>
-                        </div>
-                    </ModalWithTrigger>
-                );
-            } else {
-                if (window.OSX) {
-                    return (
-                        <a className="mx1" title="Download this data" onClick={function() {
-                            window.OSX.saveCSV(JSON.stringify(card.dataset_query));
-                        }}>
-                            <Icon name='download' size={16} />
-                        </a>
-                    );
-                } else {
-                    return (
-                        <form ref={(c) => this._downloadCsvForm = c} method="POST" action={csvUrl}>
-                            <input type="hidden" name="query" value="" />
-                            <a className="mx1" title="Download this data" onClick={() => this.onDownloadCSV()}>
-                                <Icon name='download' size={16} />
-                            </a>
-                        </form>
-                    );
-                }
-            }
-        }
-    }
-
-
     render() {
         const { card, databases, isObjectDetail, isRunning, result } = this.props
         let viz;
diff --git a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx
index 63da9c30d750e81cb397fe53d0b09c3c6282bf1b..7b6dddf3135ed10f2aa0a6bd7e6e26d7a55835a7 100644
--- a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx
+++ b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx
@@ -35,7 +35,6 @@ import {
     getParameters,
     getDatabaseFields,
     getSampleDatasetId,
-    getFullDatasetQuery,
     getNativeDatabases,
     getIsRunnable,
 } from "../selectors";
@@ -89,7 +88,6 @@ const mapStateToProps = (state, props) => {
         parameters:                getParameters(state),
         databaseFields:            getDatabaseFields(state),
         sampleDatasetId:           getSampleDatasetId(state),
-        fullDatasetQuery:          getFullDatasetQuery(state),
 
         isShowingDataReference:    state.qb.uiControls.isShowingDataReference,
         isShowingTutorial:         state.qb.uiControls.isShowingTutorial,
diff --git a/frontend/src/metabase/query_builder/selectors.js b/frontend/src/metabase/query_builder/selectors.js
index 6910117602348c3141eeff167c3c9a003d2a98b9..8fa4c20c93308ac1be4e93d8b103ab02c08c8582 100644
--- a/frontend/src/metabase/query_builder/selectors.js
+++ b/frontend/src/metabase/query_builder/selectors.js
@@ -7,7 +7,6 @@ import { getTemplateTags } from "metabase/meta/Card";
 import { isCardDirty, isCardRunnable } from "metabase/lib/card";
 import { parseFieldTarget } from "metabase/lib/query_time";
 import { isPK } from "metabase/lib/types";
-import { applyParameters } from "metabase/meta/Card";
 
 export const uiControls                = state => state.qb.uiControls;
 
@@ -153,12 +152,6 @@ export const getParameters = createSelector(
 	(implicitParameters) => implicitParameters
 );
 
-export const getFullDatasetQuery = createSelector(
-	[card, getParameters, parameterValues],
-	(card, parameters, parameterValues) =>
-		card && applyParameters(card, parameters, parameterValues)
-)
-
 export const getIsRunnable = createSelector(
 	[card, tableMetadata],
 	(card, tableMetadata) => isCardRunnable(card, tableMetadata)
diff --git a/src/metabase/api/card.clj b/src/metabase/api/card.clj
index d98c9c63e50e11b5377420491616f05df4313b22..709595bad6698693c98370d79730122571475607 100644
--- a/src/metabase/api/card.clj
+++ b/src/metabase/api/card.clj
@@ -1,5 +1,6 @@
 (ns metabase.api.card
   (:require [clojure.data :as data]
+            [cheshire.core :as json]
             [compojure.core :refer [GET POST DELETE PUT]]
             [schema.core :as s]
             (metabase.api [common :refer :all]
@@ -273,16 +274,16 @@
   (run-query-for-card card-id parameters))
 
 (defendpoint POST "/:card-id/query/csv"
-  "Run the query associated with a Card, and return its results as CSV."
-  [card-id :as {{:keys [parameters]} :body}]
-  (dataset-api/as-csv (run-query-for-card card-id parameters)))
-
-(defendpoint GET "/:card-id/json"
-  "Fetch the results of a Card as JSON."
-  [card-id]
-  (let [{{:keys [columns rows]} :data} (run-query-for-card card-id nil)]
-    (for [row rows]
-      (zipmap columns row))))
+  "Run the query associated with a Card, and return its results as CSV. Note that this expects the parameters as serialized JSON in the 'parameters' parameter"
+  [card-id parameters]
+  {parameters (s/maybe su/JSONString)}
+  (dataset-api/as-csv (run-query-for-card card-id (json/parse-string parameters keyword))))
+
+(defendpoint POST "/:card-id/query/json"
+  "Run the query associated with a Card, and return its results as JSON. Note that this expects the parameters as serialized JSON in the 'parameters' parameter"
+  [card-id parameters]
+  {parameters (s/maybe su/JSONString)}
+  (dataset-api/as-json (run-query-for-card card-id (json/parse-string parameters keyword))))
 
 
 (define-routes)
diff --git a/src/metabase/api/dataset.clj b/src/metabase/api/dataset.clj
index b9566fc7863d78fe1766f99e3a2c2a43f372b8ca..42c7789c124eb657047527b80fcc82ce8c9c7815 100644
--- a/src/metabase/api/dataset.clj
+++ b/src/metabase/api/dataset.clj
@@ -65,6 +65,20 @@
     {:status 500
      :body   (:error response)}))
 
+(defn as-json
+  "Return a JSON response containing the RESULTS of a query."
+  {:arglists '([results])}
+  [{{:keys [columns rows]} :data, :keys [status], :as response}]
+  (if (= status :completed)
+    ;; successful query, send CSV file
+    {:status  200
+     :body    (for [row rows]
+                (zipmap columns row))
+     :headers {"Content-Disposition" (str "attachment; filename=\"query_result_" (u/date->iso-8601) ".json\"")}}
+    ;; failed query, send error message
+    {:status 500
+     :body   {:error (:error response)}}))
+
 (defendpoint POST "/csv"
   "Execute a query and download the result data as a CSV file."
   [query]
@@ -73,5 +87,13 @@
     (read-check Database (:database query))
     (as-csv (qp/dataset-query query {:executed-by *current-user-id*}))))
 
+(defendpoint POST "/json"
+  "Execute a query and download the result data as a JSON file."
+  [query]
+  {query su/JSONString}
+  (let [query (json/parse-string query keyword)]
+    (read-check Database (:database query))
+    (as-json (qp/dataset-query query {:executed-by *current-user-id*}))))
+
 
 (define-routes)
diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj
index 596e5b1729326d1d455f065758fc84ae31add59d..9b4dfb570eb70d193d06fe8e33323601c7a60fb6 100644
--- a/test/metabase/api/card_test.clj
+++ b/test/metabase/api/card_test.clj
@@ -1,6 +1,7 @@
 (ns metabase.api.card-test
   "Tests for /api/card endpoints."
-  (:require [expectations :refer :all]
+  (:require [cheshire.core :as json]
+            [expectations :refer :all]
             [metabase.db :as db]
             [metabase.http-client :refer :all, :as http]
             [metabase.middleware :as middleware]
@@ -317,8 +318,7 @@
                Table     [{table-id :id}    {:db_id database-id, :name "CATEGORIES"}]
                Card      [card              {:dataset_query {:database database-id
                                                              :type     :native
-                                                             :native   {:query "SELECT COUNT(*) FROM CATEGORIES;"}
-                                                             :query    {:source-table table-id, :aggregation {:aggregation-type :count}}}}]]
+                                                             :native   {:query "SELECT COUNT(*) FROM CATEGORIES;"}}}]]
     ;; delete all permissions for this DB
     (perms/delete-related-permissions! (perms-group/all-users) (perms/object-path database-id))
     (f database-id card)))
@@ -349,4 +349,40 @@
   (do-with-temp-native-card
     (fn [database-id card]
       (perms/grant-native-read-permissions! (perms-group/all-users) database-id)
-      ((user->client :rasta) :get 200 (format "card/%d/json" (u/get-id card))))))
+      ((user->client :rasta) :post 200 (format "card/%d/query/json" (u/get-id card))))))
+
+
+;;; Test GET /api/card/:id/query/csv & GET /api/card/:id/json **WITH PARAMETERS**
+
+(defn- do-with-temp-native-card-with-params {:style/indent 0} [f]
+  (with-temp* [Database  [{database-id :id} {:details (:details (Database (id))), :engine :h2}]
+               Table     [{table-id :id}    {:db_id database-id, :name "VENUES"}]
+               Card      [card              {:dataset_query {:database database-id
+                                                             :type     :native
+                                                             :native   {:query         "SELECT COUNT(*) FROM VENUES WHERE CATEGORY_ID = {{category}};"
+                                                                        :template_tags {:category {:id           "a9001580-3bcc-b827-ce26-1dbc82429163"
+                                                                                                   :name         "category"
+                                                                                                   :display_name "Category"
+                                                                                                   :type         "number"
+                                                                                                   :required     true}}}}}]]
+    (f database-id card)))
+
+(def ^:private ^:const ^String encoded-params
+  (json/generate-string [{:type   :category
+                          :target [:variable [:template-tag :category]]
+                          :value  2}]))
+
+;; CSV
+(expect
+  (str "COUNT(*)\n"
+       "8\n")
+  (do-with-temp-native-card-with-params
+    (fn [database-id card]
+      ((user->client :rasta) :post 200 (format "card/%d/query/csv?parameters=%s" (u/get-id card) encoded-params)))))
+
+;; JSON
+(expect
+  [{(keyword "COUNT(*)") 8}]
+  (do-with-temp-native-card-with-params
+    (fn [database-id card]
+      ((user->client :rasta) :post 200 (format "card/%d/query/json?parameters=%s" (u/get-id card) encoded-params)))))