Microsoft Fabric: Writing to a Warehouse Across Workspaces from a Notebook
A practical guide to what you need, what breaks, and what actually works
If you work with Microsoft Fabric long enough, you’ll eventually run into this scenario: your data pipeline, transformation logic, or Spark notebook lives in one workspace, but the Warehouse you need to write results to lives in another workspace.
It sounds like it should be simple. It isn’t — at least not with the tools most people reach for first. This guide walks through exactly why the “obvious” approach fails, what actually works, and what you need to watch out for when setting this up.
The Scenario
- Workspace 1 — hosts your notebook and source data
- Workspace 2 — hosts the destination Warehouse and target table
You want to run your notebook in Workspace 1 and write the final output directly into the Warehouse sitting in Workspace 2 — without moving the notebook, without moving the Warehouse, and without manually exporting or importing data.
Why the “Obvious” Approach Doesn’t Work
Most people start with the native Fabric Spark connector:
dataframe.write.mode(“append”).synapsesql(
“<WarehouseName>.<SchemaName>.<TableName>”
)
This connector works beautifully — as long as the Warehouse is in the same workspace as your notebook. The moment your Warehouse is in a different workspace, this connector has no built-in way to point at another workspace. There is no workspace parameter and no datasource parameter, despite what intuition might suggest.
Key takeaway
synapsesql() is workspace-scoped. It is currently designed for same-workspace reads and writes only.
The Actual Solution: JDBC + Access Token
The commonly used approach to write across workspaces is to bypass the native connector entirely and use a standard JDBC connection authenticated with a Fabric access token.
Here is the full working pattern:
import com.microsoft.spark.fabric
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
# Step 1: Get a valid access token from the current Fabric session
token = mssparkutils.credentials.getToken(“https://database.windows.net/”)
# Step 2: Define your target Warehouse’s connection details
sql_endpoint = “<your-sql-endpoint>”
database_name = “<your-warehouse-name>”
target_table = “<your-schema>.<your-table>”
jdbc_url = (
f”jdbc:sqlserver://{sql_endpoint}:1433;”
f”database={database_name};”
f”encrypt=true;”
f”trustServerCertificate=false;”
f”hostNameInCertificate=*.datawarehouse.fabric.microsoft.com;”
f”loginTimeout=30;”
)
# Step 3: Write using JDBC with the token
your_dataframe.write \
.format(“jdbc”) \
.option(“url”, jdbc_url) \
.option(“dbtable”, target_table) \
.option(“accessToken”, token) \
.mode(“append”) \
.save()
This works regardless of which workspace the Warehouse sits in, because JDBC doesn’t care about Fabric’s internal workspace boundaries — it just needs a valid hostname, port, database name, and a credential that’s authorized.
What You Actually Need Before You Start
1. The Warehouse’s SQL Endpoint
A common mistake is trying to manually construct the hostname by joining the Warehouse ID and Workspace ID with a hyphen. Don’t do this — it produces an invalid hostname, and you’ll get a generic “TCP/IP connection failed” error that gives no indication the hostname itself is wrong.
Instead, get the real connection string directly from Fabric:
- Open the Warehouse in its home workspace
- Go to Settings → SQL endpoint (or the “SQL endpoint” item in the left navigation)
- Copy the Server value exactly as shown — a single clean hostname ending in .datawarehouse.fabric.microsoft.com
2. The Right Authentication Method
This is where most of the friction happens. There are a few options, and not all of them work in a Fabric Spark notebook:
| Auth Method | Works in Notebooks? | Notes |
| ActiveDirectoryIntegrated (Kerberos) | No | Fails with a Kerberos realm error — Spark clusters have no Kerberos realm configured |
| Username / password (SQL auth) | Usually disabled | Fabric Warehouses are Entra ID (Azure AD) only by default |
| Access Token (accessToken option) | Yes | The correct, supported approach |
| Service Principal | Yes | Needed for unattended or scheduled jobs with no interactive user |
3. The Correct Token Resource
When fetching a token via mssparkutils.credentials.getToken(…), the resource string matters a lot. Passing an intuitive-but-wrong value will fail with a “not a valid resource” error.
The resource Fabric Warehouses actually expect is the standard Azure SQL resource URI:
token = mssparkutils.credentials.getToken(“https://database.windows.net/”)
If you’re ever unsure which resource string is valid in your environment, test a few candidates in a quick loop rather than guessing one at a time:
resources = [
“https://database.windows.net/”,
“https://analysis.windows.net/powerbi/api”,
“https://storage.azure.com/”,
]
for r in resources:
try:
t = mssparkutils.credentials.getToken(r)
print(f”Works: {r}”)
except Exception as e:
print(f”Failed: {r} -> {str(e)[:80]}”)
4. Permissions on the Target Warehouse
Having a valid token isn’t enough — the identity behind that token (you, or a service principal) needs actual write permissions on the target Warehouse in the other workspace. At minimum, one of the following is required:
- Contributor (or higher) role on the target workspace, or
- Explicit ReadWrite permission on the specific Warehouse item
Important
Without this, you’ll get an authorization error even though the connection itself succeeds.
Things to Watch Out For
- Don’t manually concatenate Workspace ID + Warehouse ID. It looks plausible but produces an invalid hostname. Always copy the SQL endpoint string directly from the Fabric UI.
- Kerberos auth will not work in Spark notebooks. If you see a Kerberos realm error, you’re on the wrong auth path — switch to token-based auth rather than trying to configure a realm.
- Token resource strings are case- and format-sensitive. Short labels are not valid; use the full resource URI. When in doubt, test multiple strings.
- Tokens are short-lived. If your notebook runs for a long time before the write step, the token may expire. For long-running jobs, fetch the token right before the write operation.
- Interactive vs. scheduled runs need different auth. Interactive runs use your own signed-in identity. Scheduled pipelines with no logged-in user need a Service Principal with its own credentials and explicit permissions on the target Warehouse.
- Network and firewall rules still apply. If the target workspace has network restrictions such as private endpoints or IP allow-lists, the JDBC connection needs to be permitted to reach it.
Quick Reference: The Full Working Pattern
import com.microsoft.spark.fabric
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
# 1. Get token (use the right resource string)
token = mssparkutils.credentials.getToken(“https://database.windows.net/”)
# 2. Use the exact SQL endpoint from the target Warehouse’s settings
sql_endpoint = “<your-sql-endpoint>”
database_name = “<your-warehouse-name>”
target_table = “<your-schema>.<your-table>”
jdbc_url = (
f”jdbc:sqlserver://{sql_endpoint}:1433;”
f”database={database_name};”
f”encrypt=true;”
f”trustServerCertificate=false;”
f”hostNameInCertificate=*.datawarehouse.fabric.microsoft.com;”
f”loginTimeout=30;”
)
# 3. Write via JDBC, authenticated with the token
your_dataframe.write \
.format(“jdbc”) \
.option(“url”, jdbc_url) \
.option(“dbtable”, target_table) \
.option(“accessToken”, token) \
.mode(“append”) \
.save()
Summary
| Step | What to Do |
| 1 | Don’t use synapsesql() for cross-workspace writes — it’s same-workspace only |
| 2 | Get the exact SQL endpoint from the target Warehouse’s settings — don’t construct it manually |
| 3 | Use JDBC instead of the native connector |
| 4 | Authenticate with an access token, not Kerberos or SQL auth |
| 5 | Request the token using the full Azure SQL resource URI |
| 6 | Confirm the identity has write permissions on the target Warehouse |
| 7 | For scheduled or unattended jobs, switch to a Service Principal |
Once these pieces are in place, writing across workspaces in Fabric becomes a routine operation — the trick is simply knowing which of Fabric’s several connection methods is actually built for this job.