---
title: "Vue Scheduler Prevent Past Event Creation Example | Mobiscroll"
component: "Scheduler"
framework: "Vue"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/vue/scheduler/disallow-past-event-creation"
---

# Prevent Past Event Creation - Vue Scheduler demo

Block event creation and editing on past dates in the Vue Scheduler — past dates are blocked, past events are locked, and failed attempts show a toast. For Vue and Nuxt.

- [Live demo](https://demo.mobiscroll.com/vue/scheduler/disallow-past-event-creation)
- [View SFC JS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue/tree/main/src/demos/eventcalendar/scheduler/disallow-past-event-creation)
- [View SFC TS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue-ts/tree/main/src/demos/eventcalendar/scheduler/disallow-past-event-creation)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/scheduler/disallow-past-event-creation.md), [jQuery](https://demo.mobiscroll.com/jquery/scheduler/disallow-past-event-creation.md), [Angular](https://demo.mobiscroll.com/angular/scheduler/disallow-past-event-creation.md), [React](https://demo.mobiscroll.com/react/scheduler/disallow-past-event-creation.md)

## Description

Sometimes we don't want users to be able to create events in the past or to alter past events. This might be role based in some cases or applied in general to the calendar.

To disable past event creation and manipulation, a couple of things need to be handled:

- **Invalidate past dates & times** - this takes care of validating event drop and event creation on past dates and times that are before the past hour
- **Mark past events as fixed** - to do this, set the `editable` property of the event object to `false` for the events that should remain fixed
- **Handle past occurrences of recurring events** - recurring events are loaded as a single event and the occurrences are generated by the scheduler.

Moving the past occurrences need to be handled in the [`event-update`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdate) and [`event-create`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreate) lifecycle events

- **Give feedback to the user** - optionally, a toast can be displayed to give feedback to the user why an event cannot be dropped, moved and created in the past. For this we can use the

[`event-update-failed`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreateFailed) and [`event-create-failed`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdateFailed) lifecycle events

## What this demo shows

- A desktop weekly scheduler layout with a fixed week strip at the top, a fixed all-day row below it, and a scrollable scheduler time grid for the selected week.
- **Header navigation** The month and year label in the top left opens date navigation, while the blue previous and next arrows and the Today button on the right make it easy to move between weeks and jump back to the current day.
- **Week view** The fixed strip below the header displays the days of the selected week, with the current date highlighted by a blue circle.
- **All-day events** All-day events appear in a dedicated row that stays fixed above the time grid.
- **Time grid** The scheduler grid scrolls vertically through the hours of the selected week.
- **Past dates** Past dates are disabled and rendered with a gray background.
- **Disabled past days** Disabled past dates block event creation and drag and drop interactions.
- **Past events** Events on past dates remain visible, but they cannot be moved or repositioned.
- **Failed past updates** If a user tries to move a past recurring event, a toast appears at the bottom center with the message `Can't move past events.`
- **Timed events** Timed events are displayed in the weekly scheduler grid as colored cards with a colored stripe on the left, the event title in bold, and the exact start and end time displayed above the title.
- **Event overlapping** If events overlap, the scheduler places them side by side without hiding any of them or causing other conflicts.
- **Current time** A blue current-time line appears across the time grid, with a small blue dot marking the current date.
- **Hover behavior** Hovering over the time grid shows a time indicator that follows the cursor in 15-minute increments.
- **Event interaction** Hovering over events highlights them and shows drag and resize handles for repositioning or changing duration.
- **Event selection** Clicking an event selects and highlights it.
- **Event creation** Users can create events on future dates/hours by double-clicking the time grid or clicking and dragging on it.

## Best for

- **Booking systems** Preventing users from creating appointments in dates or times that have already passed.
- **Resource scheduling** Keeping completed or historical assignments visible while blocking accidental edits.
- **Workforce planning** Protecting past shifts, jobs, or allocations after the scheduled date has passed.
- **Service operations** Allowing teams to review past work without changing records that should remain fixed.
- **Audit-sensitive calendars** Separating future scheduling changes from historical event data that should not be moved or resized.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-themeVariant) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - option
- [`invalid`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-invalid) - option
- [`clickToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToResize) - option
- [`event-create-failed`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreateFailed) - event
- [`event-update-failed`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdateFailed) - event
- [`event-create`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreate) - event
- [`event-update`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdate) - event
- [`close`](https://mobiscroll.com/docs/vue/notifications/toast#event-onClose) - event

## Code

### SFC JS

```vue
<script setup>
import {
  getJson,
  MbscEventcalendar,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const now = new Date()
const today = new Date(now.setMinutes(59))
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1)
const myEvents = ref([])
const toastMessage = ref('')
const isToastOpen = ref(false)

const myView = {
  scheduler: {
    type: 'week'
  }
}

const myInvalid = [
  {
    recurring: {
      repeat: 'daily',
      until: yesterday
    }
  },
  {
    start: yesterday,
    end: today
  }
]

function handleEventCreateFailed(args) {
  if (!args.originEvent) {
    toastMessage.value = "Can't create event in the past"
    isToastOpen.value = true
  }
}

function handleEventUpdateFailed(args) {
  if (!args.oldEventOccurrence) {
    toastMessage.value = "Can't move event in the past"
    isToastOpen.value = true
  }
}

function handleEventCreate(args) {
  const oldEvent = args.originEvent
  const start = oldEvent && oldEvent.start ? oldEvent.start : null

  // Handle recurring events
  if (start && start < today) {
    toastMessage.value = "Can't move past event"
    isToastOpen.value = true
    return false
  }
}

function handleEventUpdate(args) {
  const oldEvent = args.oldEvent
  const start = oldEvent && oldEvent.start ? oldEvent.start : null
  const oldEventOccurrence = args.oldEventOccurrence
  const occurrenceStart =
    oldEventOccurrence && oldEventOccurrence.start ? oldEventOccurrence.start : null

  // Handle recurring events
  if ((start && start < today) || (occurrenceStart && occurrenceStart < today)) {
    return false
  }
}

function handleToastClose() {
  isToastOpen.value = false
}

onMounted(() => {
  getJson(
    'https://trial.mobiscroll.com/events/?vers=5',
    (events) => {
      for (const event of events) {
        // Convert dates to date objects
        event.start = event.start ? new Date(event.start) : event.start
        event.end = event.end ? new Date(event.end) : event.end
        // Mark past events as fixed by setting the event.editable property to false
        event.editable = event.start && today < event.start
      }
      myEvents.value = events
    },
    'jsonp'
  )
})
</script>

<template>
  <MbscEventcalendar
    :view="myView"
    :data="myEvents"
    :invalid="myInvalid"
    :clickToCreate="true"
    :dragToCreate="true"
    :dragToMove="true"
    :dragToResize="true"
    @event-create-failed="handleEventCreateFailed"
    @event-update-failed="handleEventUpdateFailed"
    @event-create="handleEventCreate"
    @event-update="handleEventUpdate"
    className="md-disallow-past-event-creation"
  />
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="handleToastClose" />
</template>

<style>
.md-disallow-past-event-creation .mbsc-readonly-event {
  opacity: 0.6;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  getJson,
  MbscEventcalendar,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventCreateEvent,
  MbscEventCreateFailedEvent,
  MbscEventUpdateEvent,
  MbscEventUpdateFailedEvent
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const now = new Date()
const today = new Date(now.setMinutes(59))
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1)
const myEvents = ref<MbscCalendarEvent[]>([])
const toastMessage = ref<string>('')
const isToastOpen = ref<boolean>(false)

const myView: MbscEventcalendarView = {
  scheduler: {
    type: 'week'
  }
}

const myInvalid = [
  {
    recurring: {
      repeat: 'daily',
      until: yesterday
    }
  },
  {
    start: yesterday,
    end: today
  }
]

function handleEventCreateFailed(args: MbscEventCreateFailedEvent) {
  if (!args.originEvent) {
    toastMessage.value = "Can't create event in the past"
    isToastOpen.value = true
  }
}

function handleEventUpdateFailed(args: MbscEventUpdateFailedEvent) {
  if (!args.oldEventOccurrence) {
    toastMessage.value = "Can't move event in the past"
    isToastOpen.value = true
  }
}

function handleEventCreate(args: MbscEventCreateEvent) {
  const oldEvent = args.originEvent
  const start = oldEvent && oldEvent.start ? oldEvent.start : null

  // Handle recurring events
  if (start && start < today) {
    toastMessage.value = "Can't move past event"
    isToastOpen.value = true
    return false
  }
}

function handleEventUpdate(args: MbscEventUpdateEvent) {
  const oldEvent = args.oldEvent
  const start = oldEvent && oldEvent.start ? oldEvent.start : null
  const oldEventOccurrence = args.oldEventOccurrence
  const occurrenceStart =
    oldEventOccurrence && oldEventOccurrence.start ? oldEventOccurrence.start : null

  // Handle recurring events
  if ((start && start < today) || (occurrenceStart && occurrenceStart < today)) {
    return false
  }
}

function handleToastClose() {
  isToastOpen.value = false
}

onMounted(() => {
  getJson(
    'https://trial.mobiscroll.com/events/?vers=5',
    (events) => {
      for (const event of events) {
        // Convert dates to date objects
        event.start = event.start ? new Date(event.start) : event.start
        event.end = event.end ? new Date(event.end) : event.end
        // Mark past events as fixed by setting the event.editable property to false
        event.editable = event.start && today < event.start
      }
      myEvents.value = events
    },
    'jsonp'
  )
})
</script>

<template>
  <MbscEventcalendar
    :view="myView"
    :data="myEvents"
    :invalid="myInvalid"
    :clickToCreate="true"
    :dragToCreate="true"
    :dragToMove="true"
    :dragToResize="true"
    @event-create-failed="handleEventCreateFailed"
    @event-update-failed="handleEventUpdateFailed"
    @event-create="handleEventCreate"
    @event-update="handleEventUpdate"
    className="md-disallow-past-event-creation"
  />
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="handleToastClose" />
</template>

<style>
.md-disallow-past-event-creation .mbsc-readonly-event {
  opacity: 0.6;
}
</style>
```

## Implementation notes

- Use `view: { scheduler: { type: 'week' } }`. Enable all four interaction modes: `clickToCreate: true`, `dragToCreate: true`, `dragToMove: true`, `dragToResize: true`.
- Compute two date references at module level: `today = new Date(now.setMinutes(59))` (current time with minutes snapped to `:59` - represents the boundary of the blocked past range) and `yesterday` (midnight of the previous day).
- Pass an `invalid` array with two entries: `{ recurring: { repeat: 'daily', until: yesterday } }` (blocks all day slots through yesterday) and `{ start: yesterday, end: today }` (a continuous block from midnight yesterday through the current `:59` mark, covering all of yesterday and the elapsed portion of today). Together these prevent creating or dropping events at any past time.
- Load events via JSONP. After loading, convert `event.start` and `event.end` to `Date` objects (`new Date(event.start)`), then set `event.editable = event.start && today < event.start` - only future events are editable; past events get `editable: false`. For the imperative API, call `inst.setEvents(events)`.
- **`onEventCreateFailed`** - show a toast ("Can't create event in the past") only when `args.originEvent` is absent (guards against false positives from recurring events).
- **`onEventUpdateFailed`** - show a toast ("Can't move event in the past") only when `args.oldEventOccurrence` is absent.
- **`onEventCreate`** - handle the recurring event edge case: if `args.originEvent?.start` is before `today`, show a toast ("Can't move past event") and `return false`. In React/Angular/Vue, `return false` alone is sufficient to restore the event - the bound array was never mutated. For the imperative API (JS/jQuery), the events array isn't bound to state, so also call `inst.updateEvent(args.originEvent)` to restore the event before returning false.
- **`onEventUpdate`** - if `args.oldEvent.start < today` or `args.oldEventOccurrence?.start < today`, `return false`. In React/Angular/Vue, `return false` alone is sufficient to restore the event - the bound array was never mutated. For the imperative API (JS/jQuery), the events array isn't bound to state, so also call `inst.updateEvent(args.oldEvent)` to restore the event.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-vue.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config vue
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/vue/getting-started/installation

## Related demos

- Previous: [Recurring Event Add & Edit](https://demo.mobiscroll.com/vue/scheduler/recurring-event-add-edit-dialog.md) - Add a recurrence editor to a Vue Scheduler add/edit dialog — with daily, weekly, monthly, and custom presets plus an RRULE builder. For Vue and Nuxt.
