If the source you want to connect to Data Studio isn’t covered by the standard Google connectors (GA4, Search Console, Ads, Sheets, BigQuery), you have two options: pay for a bridge like Supermetrics, or write your own Community Connector. This article maps out the second path, the four required Apps Script functions, the versioned deployment flow, and the maintenance cost most agencies still underestimate.
Context: on April 10, 2026, Looker Studio officially reverted to the Data Studio name1. The separation from Looker (the enterprise BI product) is now explicit, and the Community Connector development surface remained unchanged through every rename. The Apps Script Data Studio service2 is still the official development interface.
What a Community Connector Is and Isn’t
A Community Connector is an Apps Script add-on that bridges any internet-reachable data source to Data Studio. The official definition: “scripts to access and modify Data Studio Community Connectors”2. In practice it has three layers:
- Apps Script code — four required functions and helper utilities
- Manifest (
appsscript.json) — metadata so Data Studio can recognize the connector - Deployment — Head (development) and versioned (production) releases
Connectors are designed for APIs or niche sources Data Studio doesn’t know about. Common use cases:
- E-commerce platform reports (custom Shopify, WooCommerce reports)
- Niche ad networks (anything outside the big three: TikTok, Reddit Ads, Quora Ads)
- Custom CRM or HRIS systems
- Public open datasets
- Internal REST APIs
A connector is not an ETL engine (transformation belongs at the source), not a real-time stream (it’s bound to cache and refresh intervals), and not a dashboard (only a data provider).
When to Write a Community Connector
The decision turns on three properties of the target source:
| Dimension | Custom connector makes sense | Paid solution (Supermetrics, Funnel.io) makes sense |
|---|---|---|
| Source count | 1-3 niche APIs | 5+ different platforms |
| API stability | Stable, versioned | Frequent breaking changes |
| Maintenance time | 2-3 hours/month available | Maintenance must leave the in-house team |
| Budget | Zero direct cost, dev time available | $90-300/month subscription is fine |
| Privacy | Data must land in your own GCP | Third-party cloud is OK |
A line from Whatagraph’s 2026 review puts it bluntly:
“Community connectors are great in theory. Sketchy in production. If the dev who built it doesn’t maintain it, you’re on your own.”
That sentence is the one-paragraph summary you should keep in mind before committing to a custom connector. The sketchy in production warning reflects the maintenance asymmetry between the 2% of officially Google-built connectors and the 98% community-built ones: the gallery lists 24 stable Google-built connectors versus 1,188 third-party connectors.
The Four Required Functions
Every Community Connector must export four global functions. If any of them is missing, Data Studio refuses to load the connector.
getAuthType()
A minimal function that tells Data Studio which auth mechanism the connector uses.
var cc = DataStudioApp.createCommunityConnector();
function getAuthType() {
var AuthTypes = cc.AuthType;
return cc
.newAuthTypeResponse()
.setAuthType(AuthTypes.KEY)
.setHelpUrl("https://example.com/help")
.build();
}
The Apps Script Data Studio service offers seven auth types2:
| Auth type | Use case |
|---|---|
NONE | Public API, no auth needed |
OAUTH2 | Standard OAuth 2.0 (Sheets, GitHub, niche ad platforms) |
USER_PASS | Basic auth: username + password |
PATH_USER_PASS | API endpoint path + username + password |
PATH_KEY | API endpoint path + API key |
KEY | Single API key or bearer token |
USER_TOKEN | Username + personal access token |
Picking OAuth 2.0 forces extra functions: get3PAuthorizationUrls(), authCallback(), resetAuth(), isAuthValid(). OAuth 2.0 implementation is 4-5 times more code than a KEY-based auth, plus an explicit verification process.
getConfig(request)
Defines the configuration UI the user sees while adding the connector to Data Studio.
function getConfig() {
var config = cc.getConfig();
config
.newInfo()
.setId("instructions")
.setText("Enter npm package names to fetch their download count.");
config
.newTextInput()
.setId("package")
.setName("Enter package name(s), comma-separated")
.setHelpText('e.g. "googleapis" or "package,somepackage,anotherpackage"')
.setPlaceholder("googleapis")
.setAllowOverride(true);
config.setDateRangeRequired(true);
return config.build();
}
Configuration UI components: Info, TextInput, TextArea, Checkbox, SelectSingle, SelectMultiple. All builder-pattern.
For dynamic configuration (e.g. account → property → view chain), call config.setIsSteppedConfig(true). With stepped config enabled, getConfig() is re-invoked on every form change, so heavy API calls belong in getData(), not getConfig().
getSchema(request)
Defines the fields (dimensions and metrics) the connector will return.
function getFields() {
var fields = cc.getFields();
var types = cc.FieldType;
var aggregations = cc.AggregationType;
fields
.newDimension()
.setId("packageName")
.setName("Package")
.setType(types.TEXT);
fields
.newDimension()
.setId("day")
.setName("Date")
.setType(types.YEAR_MONTH_DAY);
fields
.newMetric()
.setId("downloads")
.setName("Downloads")
.setType(types.NUMBER)
.setAggregation(aggregations.SUM);
return fields;
}
function getSchema(request) {
return { schema: getFields().build() };
}
The FieldType enum has 100+ values. The most common ones in practice:
- Date:
YEAR,YEAR_MONTH,YEAR_MONTH_DAY,YEAR_MONTH_DAY_HOUR,DAY_OF_WEEK,DURATION - Geo:
COUNTRY,COUNTRY_CODE,REGION,CITY,LATITUDE_LONGITUDE - Currency:
CURRENCY_USD,CURRENCY_EUR,CURRENCY_GBP,CURRENCY_TRY(90+ ISO codes) - Numeric:
NUMBER,PERCENT,BOOLEAN - Text/media:
TEXT,URL,HYPERLINK,IMAGE,IMAGE_LINK
A practical note for international e-commerce: ISO currency codes are first-class, so you don’t need extra calculated fields to format local currency in Data Studio.
getData(request)
The function where the actual data fetch happens. The request object carries three critical parameters:
request.fields— the fields Data Studio is asking for (not always the full schema)request.configParams— user input fromgetConfig()request.dateRange— ifsetDateRangeRequired(true),{ startDate, endDate }request.lastRefresh— last refresh timestamp, critical for cache decisions
function getData(request) {
request.configParams = validateConfig(request.configParams);
var requestedFields = getFields().forIds(
request.fields.map(function (field) {
return field.name;
}),
);
try {
var apiResponse = fetchDataFromApi(request);
var normalizedResponse = normalizeResponse(request, apiResponse);
var data = getFormattedData(normalizedResponse, requestedFields);
} catch (e) {
cc.newUserError()
.setDebugText("Error fetching data from API. Exception details: " + e)
.setText(
"The connector has encountered an unrecoverable error. Please try again later, or file an issue if this error persists.",
)
.throwException();
}
return {
schema: requestedFields.build(),
rows: data,
};
}
function fetchDataFromApi(request) {
var url = [
"https://api.npmjs.org/downloads/range/",
request.dateRange.startDate,
":",
request.dateRange.endDate,
"/",
request.configParams.package,
].join("");
return UrlFetchApp.fetch(url);
}
getData() is the heart of the performance and cost layer. Three practical rules:
- Process only requested fields. Without the
requestedFields = getFields().forIds(...)filter, you’ll do extra computation and transfer on fields Data Studio never asked for. - Cache against
lastRefresh. Apps Script Cache Service3 lets you serve cached data when not enough time has passed since the last fetch. - try/catch + UserError pattern. Uncaught exceptions produce a generic “Internal error” in the chart. User-facing messages must be expressed through UserError.
Manifest
Open appsscript.json from Apps Script editor → View → Show manifest file. The Data Studio block is required:
{
"dependencies": {
"libraries": []
},
"dataStudio": {
"name": "npm Downloads",
"logoUrl": "https://raw.githubusercontent.com/npm/logos/master/npm%20square/n-64.png",
"company": "Your Company",
"companyUrl": "https://example.com",
"addonUrl": "https://example.com/connector",
"supportUrl": "https://example.com/support",
"description": "Get npm package download counts.",
"sources": ["npm"]
},
"oauthScopes": ["https://www.googleapis.com/auth/script.external_request"]
}
The oauthScopes list is zero-tolerance: every Apps Script resource your connector touches must be declared explicitly. For external HTTP requests, script.external_request; Cache Service has no separate scope (it falls under script.scriptapp implicitly). Missing scopes surface at runtime, not at publish time.
Testing with Head Deployment
Apps Script automatically updates the Head Deployment on every save. This deployment always runs the latest code; the development cycle is much faster than production releases.
Test flow:
- Apps Script editor → Deploy → Test deployments → copy the Head Deployment ID
- Open in browser:
https://datastudio.google.com/datasources/create?connectorId=HEAD_DEPLOYMENT_ID - Data Studio loads the connector and invokes
getAuthType()→getConfig()→getSchema()→getData()in sequence - After each save, refresh the browser; the new code takes effect immediately
UrlFetchApp.fetch calls are visible in Apps Script execution logs (View → Executions). For debugging, prefer console.log() over Logger.log(); the output is more readable.
Versioned Production Deployment
Because the Head Deployment changes on every save, it should never be exposed to end users. For production, keep two separate versioned deployments:
| Deployment | Purpose |
|---|---|
| Head | Development, auto-updates on every save |
| Test | Stable test version, manually pinned |
| Production | End users, manually pinned |
To ship a new version:
- Apps Script → Deploy → New deployment
- Type: Add-on (this is what Community Connectors use)
- Pin the version (auto-numbered)
- Copy the deployment ID + connector URL
- Share the URL with end users or internal teams
To update an existing deployment: Deploy → Manage deployments → Edit → pick the new version. End users do not need a re-share; the same deployment URL automatically serves the new version.
Error Handling: UserError and DebugError
The Apps Script Data Studio service offers two error object types2:
// End user sees this
cc.newUserError()
.setText("Invalid API key. Please check your credentials.")
.setDebugText("API returned 401 with body: " + responseBody)
.throwException();
// Only the developer / admin sees this
cc.newDebugError()
.setText("Schema mismatch: expected 5 columns, got 7")
.throwException();
The distinction matters: the user-friendly text and the technical debug text are different fields on the same object. DebugError is never shown to end users; it only appears in Apps Script execution logs. Messages with PII or that expose system architecture belong in UserError.debugText, not UserError.text.
Workspace Admin Context
If you’re distributing the connector inside a Google Workspace organization, validate two policy layers:
-
Is Apps Script enabled? Admin console → Apps → Google Workspace → Drive and Docs → Google Apps Script. If disabled, the connector cannot fetch data and users see:
Can't show data. Apps Script is disabled for your organization. -
Third-party connector policy? Workspace admin → Data Studio settings → Community connector data source creation. If disabled:
Community connectors are disabled for your organization.
These two policies are independent. Apps Script can be enabled while the Data Studio policy is denied. Confirming policy state with the admin before deployment cuts down “doesn’t work for users” support tickets afterwards.
Edge case where Apps Script is ON but Drive and Docs is OFF: the user cannot create or edit scripts, but existing scripts continue to run. A connector deployed in a Drive-disabled environment works normally for end users, only code editing is blocked.
Production Failure Modes
Five failure modes show up consistently after a custom connector is deployed:
| Error | Cause | Fix |
|---|---|---|
| Connector internal error | getData() failed to parse API response | try/catch + UserError, fallback data row |
Service invoked too many times in a short time: exec qps | Apps Script execution quota | Cache Service + batched UrlFetchApp calls |
| ”Apps Script is disabled for your organization” | Workspace admin disabled Apps Script | Explicit handoff to the admin |
| Real-time refresh fails | API quota or cache miss | Add an Extract Data connector as a cache layer |
| Auth fails after working initially | Token expired or scope changed | Reauthorize data source; if needed, resetAuth() |
Cache Service usage is critical. For free Workspace accounts, UrlFetchApp has a daily limit of 20,000 calls; premium accounts go above 100,000. As connector usage grows, this limit fills faster than expected. Cache pattern:
var cache = CacheService.getScriptCache();
var cacheKey =
"data_" + request.configParams.package + "_" + request.dateRange.startDate;
var cached = cache.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
var freshData = fetchDataFromApi(request);
cache.put(cacheKey, JSON.stringify(freshData), 1800); // 30 minutes
return freshData;
Cache TTL should be slightly shorter than Data Studio’s refresh interval (e.g., Data Studio refreshes every hour → cache TTL 50-55 minutes). Equal or longer TTL increases the risk of stale data.
BigQueryConfig: Running SQL from Inside the Connector
If the target data already lives in BigQuery, use the BigQueryConfig builder2. This makes Apps Script call the native BigQuery API instead of UrlFetchApp.
var config = cc
.newBigQueryConfig()
.setAccessToken(token)
.setBillingProjectId("my-project")
.setUseStandardSql(true)
.setQuery("SELECT * FROM dataset.table WHERE date = @date")
.addQueryParameter("date", cc.BigQueryParameterType.STRING, "2026-05-24");
Cost containment is three-layered:
-
Query-level limit. Set Maximum bytes billed on the BigQuery query (More → Query settings → Advanced); exceeding the limit fails the query at zero cost. Wire this limit into the connector’s SQL wrapper.
-
Schema-level discipline. Tables created with
PARTITION BY date_col CLUSTER BY high_cardinality_col+OPTIONS (require_partition_filter=true)block unfiltered queries by design. A connector user can’t accidentally trigger a full table scan. -
Zero-cost operation discipline.
SELECT DISTINCT day FROM tablescans 149 GB / $1.18 in one benchmark, while the same answer throughINFORMATION_SCHEMA.PARTITIONSscans 10 MB / $0.00008 — a 14,900x gap4. PreferINFORMATION_SCHEMAfor helper queries inside the connector.
LIMIT 1000 doesn’t reduce query cost; BigQuery scans the whole table and only the row count returned drops. To actually reduce cost, use TABLESAMPLE SYSTEM (X PERCENT) or a column subset (specific columns instead of SELECT *)4.
Maintenance: The Silent Cost
Writing a custom connector typically takes 8-16 hours. That’s not the real cost; the real cost is the post-deploy maintenance. Typical annual maintenance items:
- Adapting to upstream API breaking changes — V1 to V2 migrations, response shape changes
- OAuth scope updates — Google or third-party scope policy changes force re-auth
- Rate-limit recalibration — as users grow, the cache strategy needs revision
- Schema changes — when users request new fields, both
getSchema()andgetData()evolve in lockstep - Workspace admin policy changes — if a user organization revises Apps Script policy, support tickets follow
The Whatagraph practitioner note again:
“You can build your own or use open-source ones published by others. This adds flexibility, but it’s not plug-and-play. You’ll need technical expertise, API familiarity, and time to maintain it.”
Before committing to a custom connector, mentally sign a 24-month maintenance contract. Otherwise the connector ships, the upstream API breaks six months later, nobody fixes it, and users start silently reporting wrong data. Silent data truncation is one of the most common production failure modes observed with Data Studio5.
Distribution Options
Two distribution models after deployment:
-
Direct share. Send the deployment URL to the target user, and they create a data source via the
connectorId=...URL. Sufficient for internal teams and agency-client setups. -
Public Gallery submission. Submit to Google’s official Connector Gallery6. The review takes weeks. Once approved, the connector becomes searchable for all Data Studio users. For marketing-analytics niches, a public connector can be a brand-exposure and lead-generation channel; but approval requires solid docs and support capacity.
The direct-share model covers 90% of agency-client and internal-org scenarios. Public gallery without production-grade documentation and support hurts more than it helps; the Data Studio gallery already has abandoned-connector entries.
Decision Summary
| Question | Answer |
|---|---|
| Is the target source niche and stable? | Custom connector makes sense |
| Do you need 5+ different sources? | Supermetrics or Funnel.io makes sense |
| Can you commit 2-3 hours/month for maintenance? | Custom connector makes sense |
| Does data privacy block third-party clouds? | Custom connector makes sense |
| Is Apps Script time unavailable for the developer? | Paid solution |
| Is the data already in BigQuery? | BigQueryConfig or the native BQ connector |
The next post in this cluster, When Is Data Studio Pro Worth It?, covers Pro tier’s contribution to Community Connectors and again the maintenance economics; since Pro is a flat per-user monthly subscription, the decision hangs less on customer count and more on saved manual workload.
Niche API integration, OAuth scope design, Apps Script execution quota planning, and BigQuery cost guardrails. Answering the custom-vs-paid question for your specific scenario.
Get in TouchFootnotes
- Looker Studio is Data Studio (Google Cloud Blog, April 10, 2026) ↩
- Apps Script Data Studio Service Reference ↩ ↩2 ↩3 ↩4 ↩5
- Apps Script Cache Service ↩
- Volker Janz, “Burn Data Rather Than Money with BigQuery: The Definitive Guide” (2024) ↩ ↩2
- r/BusinessIntelligence — “Looker Studio vs Others” practitioner thread (2024) ↩
- Data Studio Connector Gallery ↩
- 01 A Community Connector is an Apps Script add-on with four required functions: getAuthType, getConfig, getSchema, getData
- 02 Use the Head Deployment URL for testing and keep versioned deployments (Test and Production) separate
- 03 Pick the auth type (OAuth 2.0, KEY, USER_PASS, PATH_KEY) based on the upstream API's authentication protocol
- 04 For Workspace audiences, validate Apps Script and Data Studio admin policies before shipping the connector
- 05 Custom connectors look free but maintenance, error handling, and adapting to breaking API changes are entirely on the developer
- 06 With BigQueryConfig, the trio of Maximum bytes billed, cluster filters, and Cache Service is the foundation of cost control
+ Do I need to know Apps Script to write a Community Connector?
Yes, Community Connectors are officially developed on Apps Script. Standard JavaScript syntax is enough, V8 runtime is the default. The Apps Script Data Studio service (DataStudioApp.createCommunityConnector) provides all builders and enums, so no separate IDE setup is required.
+ Community Connector is not working on my Workspace account, why?
Workspace admin may have disabled Apps Script entirely, or under Data Studio settings the third-party connector data source creation policy may be denied. With Apps Script disabled, the connector cannot fetch data and charts return 'Can't show data. Apps Script is disabled for your organization'. The relevant policy must be opened per org-unit by the admin.
+ Custom Community Connector or paid solutions like Supermetrics?
If the target source is a niche, stable API, a custom connector is free to start, but the maintenance commitment shifts to the developer. Supermetrics ships 100+ ready-made connectors with support, SLAs, and breaking-change adaptation. If you only need one or two APIs and can spare two hours per month on maintenance, custom is sensible. Otherwise paid lowers operational cost.
+ Does Data Studio store user credentials?
No. Data Studio does not store the credentials a user gives a connector (API key, OAuth token, username/password). On every fetch, the connector passes the credential to the third-party API and asks 'is this still valid?'. While this is by design for security, it forces the connector developer to write code resilient to token rotation and scope changes.
+ How do I cap costs if the connector runs SQL against BigQuery?
If you use the BigQueryConfig builder, the connector runs queries directly on the user's BigQuery project. Cost control requires two layers: a query-level Maximum bytes billed limit and dataset-level partition + cluster discipline (require_partition_filter=true). Inside the connector code, comparing lastRefresh with Apps Script Cache Service is critical to avoid unnecessary BigQuery queries.