100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

The setup() Function and <script setup>

Learn the entry point of the Composition API — the setup() function — and how <script setup> compiles away its boilerplate.

The Composition API in DepthBeginner8 min readJul 9, 2026
Analogies

The setup() Function and <script setup>

The setup() function is the entry point for the Composition API in Vue 3. It runs once, before the component is created, before data, computed properties, and methods exist in the Options API sense, and before the component instance itself is fully assembled. Inside setup(), you create reactive state, computed values, and functions, then return them so the template can access them. <script setup> is a compile-time syntax sugar that removes almost all of the boilerplate setup() requires — no explicit return statement, no wrapping function, and direct access to imports and top-level bindings from the template.

🏏

Cricket analogy: setup() is like the pre-match team meeting that happens once before the toss — before the scoreboard, stats, or lineup officially exist, the coach assembles the game plan and hands (returns) it to the players; <script setup> is like a modern huddle where the plan is simply understood by everyone present, no formal handout needed.

Using setup() directly

Before <script setup> existed, Composition API code lived inside an explicit setup() function defined in the component options object. Anything the template needs must be explicitly returned from setup(), which is easy to forget and adds visual noise, especially as a component grows.

🏏

Cricket analogy: Before modern digital scoreboards, a scorer had to explicitly write every stat onto a physical board (explicit return) or it simply wouldn't be visible to the crowd — forgetting to chalk up a wicket was an easy, visually noisy mistake as the scoresheet grew longer through a long innings.

vue
<script>
import { ref, computed } from 'vue'

export default {
  props: ['initialCount'],
  setup(props) {
    const count = ref(props.initialCount)
    const doubled = computed(() => count.value * 2)

    function increment() {
      count.value++
    }

    return { count, doubled, increment }
  }
}
</script>

The same component with <script setup>

<script setup> compiles to the equivalent of a setup() function under the hood, but every top-level binding — variables, functions, imports — is automatically exposed to the template without an explicit return. Props and emits are declared with the defineProps and defineEmits compiler macros, which need no import because the compiler recognizes and transforms them at build time.

🏏

Cricket analogy: Under the hood, a modern digital scoreboard still runs the same scoring logic as the old manual one, but every stat a scorer enters is automatically shown to the crowd without a manual "publish" step, and standard signals like "wide" or "no-ball" (defineProps/defineEmits) are built-in umpire calls needing no extra rulebook lookup.

vue
<script setup>
import { ref, computed } from 'vue'

const props = defineProps<{ initialCount: number }>()
const count = ref(props.initialCount)
const doubled = computed(() => count.value * 2)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">{{ count }} (doubled: {{ doubled }})</button>
</template>

What setup() receives

When used explicitly, setup(props, context) receives two arguments: a reactive props object (which should not be destructured directly, since that loses reactivity — destructure with toRefs if needed) and a context object exposing attrs, slots, and emit. <script setup> provides equivalent access through defineProps(), defineEmits(), useSlots(), and useAttrs(), so you rarely need the raw setup(props, context) signature anymore.

🏏

Cricket analogy: The explicit setup(props, context) form is like a scorer receiving a full stat sheet (props) that loses live-updating if copied field by field (must use toRefs), plus a toolkit (context) for extra signals and umpire calls (attrs/slots/emit); the modern approach uses simpler dedicated tools instead of the raw two-argument handoff.

<script setup> is now the idiomatic, recommended way to author Vue 3 components — the Vue team and most style guides treat explicit setup() as a lower-level, less ergonomic form, useful mainly for advanced patterns (like manually returning a render function) or codebases that predate the <script setup> syntax.

A common mistake when using the explicit setup(props) form is destructuring props directly, e.g. const { initialCount } = props, which breaks reactivity because it copies the primitive value out of Vue's reactive proxy at that instant. Access props.initialCount directly, or use toRefs(props) if you truly need standalone refs.

  • setup() is the Composition API's entry point, running before the component instance is fully created.
  • Explicit setup() must return an object of everything the template needs to access.
  • <script setup> is compiler sugar: top-level bindings are auto-exposed to the template, no return statement needed.
  • defineProps and defineEmits are compiler macros available without import inside <script setup>.
  • setup(props, context) exposes attrs, slots, and emit via its context argument; <script setup> offers useAttrs()/useSlots() equivalents.
  • Never destructure props directly, as it strips reactivity — access via props.x or wrap with toRefs().

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#VueJsStudyNotes#WebDevelopment#TheSetupFunctionAndScriptSetup#Setup#Function#Script#Directly#Functions#StudyNotes#SkillVeris