diff --git a/develop-docs/sdk/telemetry/spans/index.mdx b/develop-docs/sdk/telemetry/spans/index.mdx
new file mode 100644
index 0000000000000..6853b90e443fd
--- /dev/null
+++ b/develop-docs/sdk/telemetry/spans/index.mdx
@@ -0,0 +1,6 @@
+---
+title: Spans
+sidebar_order: 8
+---
+
+
diff --git a/develop-docs/sdk/telemetry/spans/scopes.mdx b/develop-docs/sdk/telemetry/spans/scopes.mdx
new file mode 100644
index 0000000000000..c5afbddc81958
--- /dev/null
+++ b/develop-docs/sdk/telemetry/spans/scopes.mdx
@@ -0,0 +1,60 @@
+---
+title: Scopes
+---
+
+
+ This is work in progress and does not represent the final state.
+
+
+The implementation in each SDK consists of three types of scopes:
+
+- The global scope
+- The isolation scope
+- The current scope
+
+Regardles of the scope type, data like tags, breadcrumbs, context, etc. can be added to each scope type by the user.
+
+### Global Scope
+
+The global scope acts as a global variable that stays the same for the entire execution of the application. Data applied to this scope will be applied to **all** events emitted by the SDK.
+This scope is used for application-wide data like the `release`, the `environment`, etc.
+
+### Isolation Scope
+
+The isolation scope holds data which is only applicable to the current request (on a server), tab (in a browser), or user (on mobile). Top-level SDK APIs like `sentry.setTag()`, `sentry.setContext()`, etc. write to the isolation scope.
+
+The isolation scope is stored in a context variable, thread local, async local, or similar depending on the platform.
+
+The isolation scope is forked by the SDK's integrations. Users should not need to think about isolation scopes or forking of one.
+
+### Current Scope
+
+This scope holds data for the current active span. Whenever a new span is started the current scope of the parent span is forked (read: duplicated), giving the new span all the data from the parent span and making it possible to add or mutate data that is just applied to the new span (see also "Copy-on-write").
+
+Changing the original scope after forking does not modify the forked scope.
+
+The current scope is stored in a context variable, thread local, async local, or similar depending on the platform.
+
+The current scope can be forked by the end user. Either explicitly, by using `sentry.withScope()` or implicitly, by starting a new span.
+
+### The Scope APIs
+
+```js
+...
+```
+
+### Applying the scope data to events
+
+The data held by all three scope types is merged before it is applied to an event.
+
+This is performed in the following order:
+
+1. Data from the global scope is...
+2. merged with data from the isolation scope, which is...
+3. merged with data from the current scope, which is ...
+4. applied to the event
+
+## Related Documents
+
+This is a more concise version of the [Hub & Scope Refactoring](/sdk/miscellaneous/hub_and_scope_refactoring/) document. It does focus on the actual implementation and expected features.
+The old document remains unchanged, and offers more historical context and migration strategies.
diff --git a/develop-docs/sdk/telemetry/spans/span-api.mdx b/develop-docs/sdk/telemetry/spans/span-api.mdx
new file mode 100644
index 0000000000000..cefe918082933
--- /dev/null
+++ b/develop-docs/sdk/telemetry/spans/span-api.mdx
@@ -0,0 +1,57 @@
+---
+title: Span API
+---
+
+Spans are measuring the duration of certain operations in an application.
+The topmost member of a span tree is called the root span. This span has no parent span and groups together its children with a representative name for the entire operation, such as `GET /` in case of a request to a backend application.
+
+## Creating a root span
+
+The SDK must expose a method for creating a root span. The user must be able to set certain properties on this root span, such as its name, the type of operation (`op`) and others.
+
+```js
+span = sentry.tracing.startSpan()
+ ->setName('GET /')
+ ->setOp('http.server')
+
+span.end()
+```
+
+## Creating nested spans
+
+To create nested spans, the SDK must expose an explicit way for a user to perform this task.
+
+Additionally, the SDK may expose alternative APIs to create nested spans, such as allowing a user to wrap an operation into a callback or apply a decorator to certain blocks. These alternative APIs must never create a root span and no-op if no parent span is present.
+
+```js
+childSpan = span.startChild()
+ ->setName('authentication middleware')
+ ->setOp('middleware.handle')
+
+childSpan.end()
+```
+
+## Setting the span status
+
+A span has two statuses, `ok` and `error`. By default, the status of a span is set to `ok`.
+The SDK must allow a user to modify the status of a span.
+
+```js
+span.setStatus('error')
+```
+
+## Setting span attributes
+
+The SDK must expose a method to allow a user to set data attributes onto a span.
+These attributes should use pre-defined keys whenever possible.
+
+```js
+span.setAttribute(SpanAttributes.HTTP_METHOD, 'GET')
+span.setAttribute(SpanAttributes.HTTP_RESPONSE_STATUS_CODE, 200)
+```
+
+## Additional, optional span APIs
+
+`span.setStartTimestamp()` - overwrite the span's start time
+
+`span.setEndTimestamp()` - overwrites the span's end time
diff --git a/develop-docs/sdk/telemetry/spans/span-protocol.mdx b/develop-docs/sdk/telemetry/spans/span-protocol.mdx
new file mode 100644
index 0000000000000..1de76abe4e9ae
--- /dev/null
+++ b/develop-docs/sdk/telemetry/spans/span-protocol.mdx
@@ -0,0 +1,65 @@
+---
+title: Span Protocol
+---
+
+
+ This is work in progress and does not represent the final state.
+
+
+The SDK must implement a new "span" envelope item, which is used to emit an entire span tree (one root span and its children).
+The payload of each envelope item follows the [OpenTelemetry Protocol](https://opentelemetry.io/docs/specs/otel/protocol/).
+
+```
+{
+ "event_id":"9ec79c33ec9942ab8353589fcb2e04dc"
+}
+{
+ "type": "span"
+}
+{
+ "traceId": "32d3c7cb501fbddbe3ce1016a72d50b5",
+ "spanId": "e91d37480970523b",
+ "name": "GET /",
+ "startTime": "1544712660",
+ "endTime": "1544712680",
+ "attributes": [
+ {
+ "key": "sentry.op",
+ "value": {
+ "stringValue": "http.sever",
+ }
+ },
+ {
+ "key": "http.response.status_code",
+ "value": {
+ "intValue": "200",
+ }
+ }
+ ]
+}
+{
+ "type": "span"
+}
+{
+ "traceId": "32d3c7cb501fbddbe3ce1016a72d50b5",
+ "spanId": "6b22b3af586e777a",
+ "parentSpanId": "e91d37480970523b",
+ "name": "UserMiddleware",
+ "startTimeUnix": "1544712665",
+ "endTimeUnix": "1544712675",
+ "attributes": [
+ {
+ "key": "sentry.op",
+ "value": {
+ "stringValue": "middleware.handle",
+ }
+ },
+ {
+ "key": "user.email",
+ "value": {
+ "stringValue": "jane.doe@example.com",
+ }
+ }
+ ]
+}
+```
diff --git a/develop-docs/sdk/telemetry/spans/span-sampling.mdx b/develop-docs/sdk/telemetry/spans/span-sampling.mdx
new file mode 100644
index 0000000000000..04cd8d7ac047b
--- /dev/null
+++ b/develop-docs/sdk/telemetry/spans/span-sampling.mdx
@@ -0,0 +1,102 @@
+---
+title: Span Sampling & Filtering
+---
+
+Any APIs exposed to the user to sample or filter spans must adhere to the following design principles:
+
+- The APIs are optimized for trace completeness
+- The APIs are optimized for conclusive sampling decisions
+
+## Sample root spans with `tracesSampleRate` & `tracesSampler`
+
+The SDK is automatically initialized with a `tracesSampleRate` of `0`.
+When starting a root span, the configured rate is compared against a random number between 0 and 1 to decide if this root span will be sampled or not.
+
+If the SDK is configured with a `tracesSampler`, the `tracesSampleRate` no longer applies.
+The `tracesSampler` callback must receive sufficient arguments from users to define their own sampling rules.
+This can include but is not limited to certain attributes from the root span, such as HTTP headers.
+The return value of the `tracesSampler` is a float between `0.0` and `1.0`.
+
+```js
+Sentry.init({
+ tracesSampler: ({ name, attributes, parentSampleRate }) => {
+ // Inherit the trace parent's sample rate if there is one. Sampling is deterministic
+ // for one trace, e.g. if the parent was sampled, all children will be sampled at the same rate.
+ if (typeof parentSampleRate === "number") {
+ return parentSampleRate;
+ }
+
+ // Else, use a default sample rate (replacing tracesSampleRate).
+ return 0.5;
+ }
+})
+```
+
+The `parentSampleRate` is a propagated value inside the baggage, using key `sentry-sample_rand`.
+The value stems from a truly random number between 0 and 1, generated when a new trace is started. If the SDK does not receive such a number in an incoming trace, a new, truly random number between 0 and 1 is generated.
+
+In the following cases, the SDK must compare sample rates against this `parentSampleRate` instead of `math.random()`:
+
+ - When a `tracesSampler` is configured, i.e. `trace["sentry-sample_rand"] < tracesSampler()`
+
+ - When the SDK is the head of trace, this applies to sample decisions based on `tracesSampleRate`, e.g. `trace['sentry-sample_rand'] < config.tracesSampleRate`
+
+If the `sentry-sample_rate` (`parentSampleRate`) is not available for any reason for an inbound trace, but the trace has the sampled flag set to true, the SDK injects `parentSampleRate: 1.0` into the `tracesSampler`.
+
+If no `tracesSampler` is configured, a propagated sampling decision via the traceparent takes precedence over the `tracesSampleRate`. This behavior can be disabled by defining a `tracesSampler`.
+
+## Parent Sampling Origins
+
+If the SDK can parse an org ID from the configured DSN, this value must be propagated as a baggage entry with the key `sentry-org`. Given a DSN of `https://1234@o1.ingest.us.sentry.io/1`, the org ID is 1, based on `o1`.
+
+On incoming traces, the SDK must compare the `sentry-org` baggage value against its own parsed value from the DSN. Only if both match, the parent sampling decisions applies.
+
+This behavior can be disabled by setting `strictTracePropagation: false` in the SDK init call.
+
+The SDK must be configurable with an optional `org: ` setting that takes precedence over the parsed value from the DSN.
+
+## Filter spans with `ignoreSpans` & integration config
+
+The SDK must implement a mechanism for users to filter out spans. The result must be binary (true/false).
+The `ignoreSpans` option accepts a glob pattern or string.
+The `integrations` option can perform in similar fashion or make explicit opt-out possible via a bool flag.
+
+If both options are not feasible to be implemented in certain SDKs, other approaches must be explored that have the same outcome.
+
+```js
+Sentry.init({
+ ignoreSpans: [
+ 'GET /about',
+ 'events.signal *',
+ ],
+ integrations: [
+ fsIntegration: {
+ ignoreSpans: [
+ 'fs.read',
+ ],
+ readSpans: true,
+ writeSpans: false,
+ }
+ ]
+})
+```
+
+## Sanitize span attributes with `beforeSendSpans`
+
+This callback must not allow the removal of any spans from the span tree.
+It receives a deep copy of all spans in the span tree and their attributes.
+
+```
+[
+ {
+ 'name': 'GET /',
+ 'attributes': [
+ 'http.request.method': 'GET',
+ 'http.response.status_code': 200,
+ ]
+ },
+]
+```
+
+Users can mutate any exposed properties to perform sanitation on sensitive data or Pii.
+The return value `beforeSendSpans` should be merged with the original span tree prior to emission.
diff --git a/develop-docs/sdk/telemetry/spans/span-trace-propagation.mdx b/develop-docs/sdk/telemetry/spans/span-trace-propagation.mdx
new file mode 100644
index 0000000000000..e95a4f6dc9d2c
--- /dev/null
+++ b/develop-docs/sdk/telemetry/spans/span-trace-propagation.mdx
@@ -0,0 +1,24 @@
+---
+title: Span Trace Propagation
+---
+
+## Continue an incoming trace
+
+To continue a trace from an upstream service, the SDK must expose a method to extract the traceparent and baggage information and apply these to the applicable scope.
+
+```js
+scope.setPropagationContext({
+ traceparent: request.headers.SENTRY_TRACE,
+ baggage: request.headers.SENTRY_BAGGAGE,
+})
+```
+Newly created root spans should now contain these properties, such as `trace_id` and `parent_span_id`.
+
+## Continue an outgoing trace
+
+To propagate a trace to a downstream service, the SDK must expose methods to fetch the required information to allow the next service to continue the trace.
+
+```js
+traceparent = scope.getTraceparent()
+baggage = scope.getBaggage()
+```